errors/
errors.rs

1use std::time::Instant;
2
3use anyhow::{self, Context};
4use mini_async_repl::{
5    command::{
6        lift_validation_err, validate, Command, CommandArgInfo, CommandArgType, Critical,
7        ExecuteCommand,
8    },
9    CommandStatus, Repl,
10};
11use std::future::Future;
12use std::pin::Pin;
13
14struct OkCommandHandler {}
15impl OkCommandHandler {
16    pub fn new() -> Self {
17        Self {}
18    }
19    async fn handle_command(&mut self) -> anyhow::Result<CommandStatus> {
20        Ok(CommandStatus::Done)
21    }
22}
23impl ExecuteCommand for OkCommandHandler {
24    fn execute(
25        &mut self,
26        args: Vec<String>,
27        args_info: Vec<CommandArgInfo>,
28    ) -> Pin<Box<dyn Future<Output = anyhow::Result<CommandStatus>> + '_>> {
29        let valid = validate(args.clone(), args_info.clone());
30        if let Err(e) = valid {
31            return Box::pin(lift_validation_err(Err(e)));
32        }
33        Box::pin(self.handle_command())
34    }
35}
36
37struct RecoverableErrorHandler {}
38impl RecoverableErrorHandler {
39    pub fn new() -> Self {
40        Self {}
41    }
42    async fn handle_command(&mut self, text: String) -> anyhow::Result<CommandStatus> {
43        Self::may_throw(text)?;
44        Ok(CommandStatus::Done)
45    }
46    // this could be any function returning Result with an error implementing Error
47    // here for simplicity we make use of the Other variant of std::io::Error
48    fn may_throw(description: String) -> Result<(), std::io::Error> {
49        Err(std::io::Error::new(std::io::ErrorKind::Other, description))
50    }
51}
52impl ExecuteCommand for RecoverableErrorHandler {
53    fn execute(
54        &mut self,
55        args: Vec<String>,
56        args_info: Vec<CommandArgInfo>,
57    ) -> Pin<Box<dyn Future<Output = anyhow::Result<CommandStatus>> + '_>> {
58        let valid = validate(args.clone(), args_info.clone());
59        if let Err(e) = valid {
60            return Box::pin(lift_validation_err(Err(e)));
61        }
62        Box::pin(self.handle_command(args[0].clone()))
63    }
64}
65
66struct CriticalErrorHandler {}
67impl CriticalErrorHandler {
68    pub fn new() -> Self {
69        Self {}
70    }
71    async fn handle_command(&mut self, text: String) -> anyhow::Result<CommandStatus> {
72        // Short notation using the Critical trait
73        Self::may_throw(text).into_critical()?;
74        Ok(CommandStatus::Done)
75    }
76    // this could be any function returning Result with an error implementing Error
77    // here for simplicity we make use of the Other variant of std::io::Error
78    fn may_throw(description: String) -> Result<(), std::io::Error> {
79        Err(std::io::Error::new(std::io::ErrorKind::Other, description))
80    }
81}
82impl ExecuteCommand for CriticalErrorHandler {
83    fn execute(
84        &mut self,
85        args: Vec<String>,
86        args_info: Vec<CommandArgInfo>,
87    ) -> Pin<Box<dyn Future<Output = anyhow::Result<CommandStatus>> + '_>> {
88        let valid = validate(args.clone(), args_info.clone());
89        if let Err(e) = valid {
90            return Box::pin(lift_validation_err(Err(e)));
91        }
92        Box::pin(self.handle_command(args[0].clone()))
93    }
94}
95
96struct RouletteErrorHandler {
97    start: Instant,
98}
99impl RouletteErrorHandler {
100    pub fn new(start: Instant) -> Self {
101        Self { start }
102    }
103    async fn handle_command(&mut self) -> anyhow::Result<CommandStatus> {
104        let ns = Instant::now().duration_since(self.start).as_nanos();
105        let cylinder = ns % 6;
106        match cylinder {
107            0 => Self::may_throw("Bang!".into()).into_critical()?,
108            1..=2 => Self::may_throw("Blank cartridge?".into())?,
109            _ => (),
110        }
111        Ok(CommandStatus::Done)
112    }
113    // this could be any function returning Result with an error implementing Error
114    // here for simplicity we make use of the Other variant of std::io::Error
115    fn may_throw(description: String) -> Result<(), std::io::Error> {
116        Err(std::io::Error::new(std::io::ErrorKind::Other, description))
117    }
118}
119impl ExecuteCommand for RouletteErrorHandler {
120    fn execute(
121        &mut self,
122        args: Vec<String>,
123        args_info: Vec<CommandArgInfo>,
124    ) -> Pin<Box<dyn Future<Output = anyhow::Result<CommandStatus>> + '_>> {
125        let valid = validate(args.clone(), args_info.clone());
126        if let Err(e) = valid {
127            return Box::pin(lift_validation_err(Err(e)));
128        }
129        Box::pin(self.handle_command())
130    }
131}
132
133#[tokio::main]
134async fn main() -> anyhow::Result<()> {
135    #[rustfmt::skip]
136    let mut repl = Repl::builder()
137        .add("ok", Command::new(
138            "Run a command that just succeeds",
139            vec![],
140            Box::new(OkCommandHandler::new()),
141        ))
142        .add("error", Command::new(
143            "Command with recoverable error handled by the REPL",
144            vec![CommandArgInfo::new_with_name(CommandArgType::String, "text")],
145            Box::new(RecoverableErrorHandler::new()),
146        ))
147        .add("critical", Command::new(
148            "Command returns a critical error that must be handled outside of REPL",
149            vec![CommandArgInfo::new_with_name(CommandArgType::String, "text")],
150            Box::new(CriticalErrorHandler::new()),
151        ))
152        .add("roulette", Command::new(
153            "Feeling lucky?",
154            vec![],
155            Box::new(RouletteErrorHandler::new(Instant::now())),
156        ))
157        .build()
158        .context("Failed to create repl")?;
159
160    let repl_res = repl.run().await;
161    match repl_res {
162        Ok(_) => Ok(()),
163        Err(_) => {
164            println!("Repl halted. Quitting.");
165            Ok(())
166        }
167    }
168}