Skip to main content

hypershell_tokio_components/providers/
simple_exec.rs

1use core::fmt::Debug;
2use core::marker::PhantomData;
3use std::process::Output;
4
5use cgp::extra::handler::{CanHandle, Handler, HandlerComponent};
6use cgp::prelude::*;
7use hypershell_components::dsl::SimpleExec;
8use tokio::io::AsyncWriteExt;
9use tokio::process::Child;
10
11use crate::dsl::CoreExec;
12
13pub struct ExecOutputError {
14    pub output: Output,
15}
16
17#[cgp_new_provider]
18impl<Context, CommandPath, Args, Input> Handler<Context, SimpleExec<CommandPath, Args>, Input>
19    for HandleSimpleExec
20where
21    Context: CanHandle<CoreExec<CommandPath, Args>, (), Output = Child>
22        + for<'a> CanRaiseAsyncError<ExecOutputError>
23        + CanWrapAsyncError<StdinPipeError>
24        + CanWrapAsyncError<WaitWithOutputError>
25        + CanRaiseAsyncError<std::io::Error>,
26    CommandPath: Send,
27    Args: Send,
28    Input: Send + AsRef<[u8]>,
29{
30    type Output = Vec<u8>;
31
32    async fn handle(
33        context: &Context,
34        _tag: PhantomData<SimpleExec<CommandPath, Args>>,
35        input: Input,
36    ) -> Result<Vec<u8>, Context::Error> {
37        let mut child = context.handle(PhantomData, ()).await?;
38
39        let input_bytes = input.as_ref();
40
41        if !input_bytes.is_empty() {
42            if let Some(mut stdin) = child.stdin.take() {
43                stdin
44                    .write_all(&input_bytes)
45                    .await
46                    .map_err(Context::raise_error)
47                    .map_err(|e| Context::wrap_error(e, StdinPipeError))?;
48            }
49        }
50
51        let output = child
52            .wait_with_output()
53            .await
54            .map_err(Context::raise_error)
55            .map_err(|e| Context::wrap_error(e, WaitWithOutputError))?;
56
57        if output.status.success() {
58            Ok(output.stdout)
59        } else {
60            Err(Context::raise_error(ExecOutputError { output }))
61        }
62    }
63}
64
65pub struct StdinPipeError;
66
67impl Debug for StdinPipeError {
68    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69        write!(f, "error piping input to stdin of child process")
70    }
71}
72
73pub struct WaitWithOutputError;
74
75impl Debug for WaitWithOutputError {
76    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77        write!(f, "error waiting for output from child process")
78    }
79}
80
81impl Debug for ExecOutputError {
82    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        write!(
84            f,
85            "child process exited with non-success code {:?}, stderr: {}",
86            self.output.status.code(),
87            String::from_utf8_lossy(&self.output.stderr),
88        )
89    }
90}