rskit-process 0.2.0-alpha.5

Process and subprocess execution with timeout and signal handling
Documentation
use std::io::ErrorKind;
use std::process::Stdio;

use tokio::{process::ChildStdin, task::JoinHandle};

use crate::{
    AppError, AppResult, CapturedIo, ErrorCode, InputPolicy, ObservedIo, OutputPolicy,
    ProcessConfig,
};

pub(super) trait PipeIo {
    fn input(&self) -> &InputPolicy;
    fn output(&self) -> &OutputPolicy;
}

impl PipeIo for CapturedIo {
    fn input(&self) -> &InputPolicy {
        &self.input
    }

    fn output(&self) -> &OutputPolicy {
        &self.output
    }
}

impl PipeIo for ObservedIo {
    fn input(&self) -> &InputPolicy {
        &self.input
    }

    fn output(&self) -> &OutputPolicy {
        &self.output
    }
}

pub(super) fn stdin_stdio(input: &InputPolicy) -> Stdio {
    match input {
        InputPolicy::Closed => Stdio::null(),
        InputPolicy::Bytes(_) => Stdio::piped(),
        InputPolicy::Inherit => Stdio::inherit(),
    }
}

pub(super) fn pipe_stdin_stdio(input: &InputPolicy) -> AppResult<Stdio> {
    match input {
        InputPolicy::Closed => Ok(Stdio::null()),
        InputPolicy::Bytes(_) => Ok(Stdio::piped()),
        InputPolicy::Inherit => Err(AppError::invalid_input(
            "process.io.input",
            "inherited stdin requires inherited I/O mode; pipe-backed interactive stdin is not supported",
        )),
    }
}

pub(super) fn inherited_config(config: &ProcessConfig) -> ProcessConfig {
    let mut config = config.clone();
    config.signal = config
        .signal
        .with_create_process_group(false)
        .with_terminate_descendants(false);
    config
}

pub(super) fn spawn_stdin_writer(
    stdin: Option<ChildStdin>,
    input: &InputPolicy,
) -> Option<JoinHandle<AppResult<()>>> {
    let InputPolicy::Bytes(bytes) = input else {
        return None;
    };
    let mut stdin = stdin?;
    let bytes = bytes.clone();
    Some(tokio::spawn(async move {
        use tokio::io::AsyncWriteExt;

        match stdin.write_all(&bytes).await {
            Ok(()) => Ok(()),
            Err(error) if error.kind() == ErrorKind::BrokenPipe => Ok(()),
            Err(error) => Err(AppError::new(
                ErrorCode::Internal,
                format!("failed to write to stdin: {error}"),
            )),
        }
    }))
}