hypershell_tokio_components/providers/
simple_exec.rs1use 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}