1pub use self::slim_deserialize::{FromSlimReader, FromSlimReaderError};
2pub use self::slim_serialize::ToSlimString;
3use std::{
4 fmt::Display,
5 io::{BufReader, Read, Write},
6};
7use thiserror::Error;
8use ulid::Ulid;
9
10mod slim_deserialize;
11mod slim_serialize;
12
13pub struct SlimConnection<R, W>
14where
15 R: Read,
16 W: Write,
17{
18 reader: BufReader<R>,
19 writer: W,
20 _version: SlimVersion,
21 closed: bool,
22}
23
24#[derive(Debug, Error)]
25pub enum NewSlimConnectionError {
26 #[error(transparent)]
27 IoError(#[from] std::io::Error),
28 #[error(transparent)]
29 SlimVersionReadError(#[from] SlimVersionReadError),
30}
31
32#[derive(Debug, Error)]
33pub enum SendInstructionsError {
34 #[error(transparent)]
35 IoError(#[from] std::io::Error),
36 #[error(transparent)]
37 FromSlimReaderError(#[from] FromSlimReaderError),
38}
39
40impl<R, W> SlimConnection<R, W>
41where
42 R: Read,
43 W: Write,
44{
45 pub fn new(mut reader: R, writer: W) -> Result<Self, NewSlimConnectionError> {
46 let mut buf = [0_u8; 13];
47 reader.read_exact(&mut buf)?;
48 let version = SlimVersion::from_str(String::from_utf8_lossy(&buf))?;
49 Ok(Self {
50 reader: BufReader::new(reader),
51 writer,
52 _version: version,
53 closed: false,
54 })
55 }
56
57 pub fn send_instructions(
58 &mut self,
59 data: &[Instruction],
60 ) -> Result<Vec<InstructionResult>, SendInstructionsError> {
61 self.writer.write_all(data.to_slim_string().as_bytes())?;
62 Ok(Vec::from_reader(&mut self.reader)?)
63 }
64
65 pub fn close(mut self) -> Result<(), std::io::Error> {
66 self.say_goodbye()?;
67 self.closed = true;
68 Ok(())
69 }
70
71 fn say_goodbye(&mut self) -> Result<(), std::io::Error> {
72 self.writer.write_all("bye".to_slim_string().as_bytes())?;
73 self.writer.flush()?;
74 Ok(())
75 }
76}
77
78impl<R, W> Drop for SlimConnection<R, W>
79where
80 R: Read,
81 W: Write,
82{
83 fn drop(&mut self) {
84 if !self.closed {
85 self.say_goodbye().expect("Error sending goodbye");
86 }
87 }
88}
89
90#[derive(Debug, PartialEq, Eq)]
91pub enum SlimVersion {
92 V0_3,
93 V0_4,
94 V0_5,
95}
96
97#[derive(Debug, Error)]
98pub enum SlimVersionReadError {
99 #[error("Invalid slim version string")]
100 Invalid,
101 #[error("Version {0} not recognized")]
102 NotRecognized(String),
103}
104
105impl SlimVersion {
106 fn from_str(string: impl AsRef<str>) -> Result<Self, SlimVersionReadError> {
107 let (_, version) = string
108 .as_ref()
109 .split_once(" -- ")
110 .ok_or(SlimVersionReadError::Invalid)?;
111 Ok(match version.trim() {
112 "V0.3" => SlimVersion::V0_3,
113 "V0.4" => SlimVersion::V0_4,
114 "V0.5" => SlimVersion::V0_5,
115 v => return Err(SlimVersionReadError::NotRecognized(v.into())),
116 })
117 }
118}
119
120#[derive(Debug, PartialEq, Eq, Clone)]
121pub struct Id(String);
122
123impl Id {
124 pub fn new() -> Self {
125 Self::default()
126 }
127}
128
129impl From<String> for Id {
130 fn from(value: String) -> Self {
131 Self(value)
132 }
133}
134
135impl From<&str> for Id {
136 fn from(value: &str) -> Self {
137 Self(value.into())
138 }
139}
140
141impl Default for Id {
142 fn default() -> Self {
143 Self(Ulid::new().to_string())
144 }
145}
146
147impl Display for Id {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(f, "{}", self.0)
150 }
151}
152
153#[derive(Debug, PartialEq, Eq, Clone)]
154pub enum Instruction {
155 Import {
156 id: Id,
157 path: String,
158 },
159 Make {
160 id: Id,
161 instance: String,
162 class: String,
163 args: Vec<String>,
164 },
165 Call {
166 id: Id,
167 instance: String,
168 function: String,
169 args: Vec<String>,
170 },
171 #[allow(dead_code)]
172 CallAndAssign {
173 id: Id,
174 symbol: String,
175 instance: String,
176 function: String,
177 args: Vec<String>,
178 },
179 #[allow(dead_code)]
180 Assign {
181 id: Id,
182 symbol: String,
183 value: String,
184 },
185}
186
187#[derive(Debug, PartialEq, Eq)]
188pub struct InstructionResult {
189 pub id: Id,
190 pub value: InstructionResultValue,
191}
192
193impl InstructionResult {
194 pub fn new(id: Id, value: InstructionResultValue) -> Self {
195 Self { id, value }
196 }
197
198 pub fn ok(id: Id) -> Self {
199 Self {
200 id,
201 value: InstructionResultValue::Ok,
202 }
203 }
204 pub fn void(id: Id) -> Self {
205 Self {
206 id,
207 value: InstructionResultValue::Void,
208 }
209 }
210 pub fn exception(id: Id, message: ExceptionMessage) -> Self {
211 Self {
212 id,
213 value: InstructionResultValue::Exception(message),
214 }
215 }
216 pub fn string(id: Id, string: String) -> Self {
217 Self {
218 id,
219 value: InstructionResultValue::String(string),
220 }
221 }
222 pub fn list(id: Id, values: Vec<InstructionResultValue>) -> Self {
223 Self {
224 id,
225 value: InstructionResultValue::List(values),
226 }
227 }
228}
229
230#[derive(Debug, PartialEq, Eq, Clone)]
231pub enum InstructionResultValue {
232 Ok,
233 Void,
234 Exception(ExceptionMessage),
235 String(String),
236 List(Vec<InstructionResultValue>),
237}
238
239impl Display for InstructionResultValue {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 match self {
242 InstructionResultValue::Ok => write!(f, "OK")?,
243 InstructionResultValue::Void => write!(f, "VOID")?,
244 InstructionResultValue::Exception(message) => write!(
245 f,
246 "Exception `{}`",
247 message.pretty_message().unwrap_or(message.raw_message())
248 )?,
249 InstructionResultValue::String(value) => write!(f, "`{}`", value)?,
250 InstructionResultValue::List(value) => write!(
251 f,
252 "[{}]",
253 value
254 .iter()
255 .map(|v| v.to_string())
256 .collect::<Vec<String>>()
257 .join(",")
258 )?,
259 }
260 Ok(())
261 }
262}
263
264#[derive(PartialEq, Eq, Debug, Clone)]
265pub struct ExceptionMessage(String);
266
267#[derive(Debug, Error)]
268#[error("Failed processing exception {0}")]
269pub struct ExceptionPrettyMessageError(String);
270
271impl ExceptionMessage {
272 pub fn new(message: String) -> Self {
273 Self(message)
274 }
275
276 pub fn raw_message(&self) -> &str {
277 &self.0
278 }
279
280 pub fn pretty_message(&self) -> Result<&str, ExceptionPrettyMessageError> {
281 if let Some(pos) = self.0.find("message:<<") {
282 let (_, rest) = self.0.split_at(pos + 10);
283 let Some((message, _)) = rest.split_once(">>") else {
284 return Err(ExceptionPrettyMessageError(self.0.clone()));
285 };
286 Ok(message)
287 } else {
288 Ok(&self.0)
289 }
290 }
291}
292
293#[derive(Debug, PartialEq, Eq)]
294pub enum ByeOrSlimInstructions {
295 Bye,
296 Instructions(Vec<Instruction>),
297}
298
299#[cfg(test)]
300mod test {
301 use std::error::Error;
302 use std::io::Cursor;
303
304 use super::*;
305
306 #[test]
307 fn test_simple_connection() -> Result<(), Box<dyn Error>> {
308 let mut writer = Vec::new();
309 let connection =
310 SlimConnection::new(Cursor::new(b"Slim -- V0.5\n"), Cursor::new(&mut writer))?;
311 assert_eq!(SlimVersion::V0_5, connection._version);
312 drop(connection);
313 assert_eq!("000003:bye".to_string(), String::from_utf8_lossy(&writer));
314 Ok(())
315 }
316
317 #[test]
318 fn test_send_instructions_connection() -> Result<(), Box<dyn Error>> {
319 let mut writer = Vec::new();
320 let mut connection = SlimConnection::new(
321 Cursor::new(b"Slim -- V0.5\n000197:[000003:000053:[000002:000026:01HFM0NQM3ZS6BBX0ZH6VA6DJX:000002:OK:]:000055:[000002:000026:01HFM0NQM3ZS6BBX0ZH6VA6DJX:000004:null:]:000056:[000002:000026:01HFM0NQM3ZS6BBX0ZH6VA6DJX:000005:Hello:]:]"),
322 Cursor::new(&mut writer),
323 )?;
324 let id = Id::from("01HFM0NQM3ZS6BBX0ZH6VA6DJX");
325 let result = connection.send_instructions(&[
326 Instruction::Import {
327 id: id.clone(),
328 path: "Path".into(),
329 },
330 Instruction::Call {
331 id: id.clone(),
332 instance: "Instance".into(),
333 function: "Function".into(),
334 args: Vec::new(),
335 },
336 Instruction::Call {
337 id: id.clone(),
338 instance: "Instance".into(),
339 function: "Function".into(),
340 args: Vec::new(),
341 },
342 ])?;
343 drop(connection);
344 assert_eq!(
345 vec![
346 InstructionResult {
347 id: id.clone(),
348 value: InstructionResultValue::Ok
349 },
350 InstructionResult {
351 id: id.clone(),
352 value: InstructionResultValue::String("null".into()),
353 },
354 InstructionResult {
355 id: id.clone(),
356 value: InstructionResultValue::String("Hello".into()),
357 }
358 ],
359 result
360 );
361 assert_eq!(
362 "000276:[000003:000069:[000003:000026:01HFM0NQM3ZS6BBX0ZH6VA6DJX:000006:import:000004:Path:]:000087:[000004:000026:01HFM0NQM3ZS6BBX0ZH6VA6DJX:000004:call:000008:Instance:000008:Function:]:000087:[000004:000026:01HFM0NQM3ZS6BBX0ZH6VA6DJX:000004:call:000008:Instance:000008:Function:]:]000003:bye".to_string(),
363 String::from_utf8_lossy(&writer)
364 );
365 Ok(())
366 }
367}