Skip to main content

tokio_process_tools/process/
builder.rs

1use super::name::{ProcessName, generate_name};
2use super::stream_config::{ProcessStreamBuilder, ProcessStreamConfig};
3use crate::error::SpawnError;
4use crate::output_stream::OutputStream;
5use crate::process_handle::ProcessHandle;
6use std::marker::PhantomData;
7
8#[doc(hidden)]
9pub struct Unnamed;
10
11#[doc(hidden)]
12pub struct Named {
13    name: ProcessName,
14}
15
16#[doc(hidden)]
17pub struct Unset;
18
19/// Typestate builder for configuring and spawning a process.
20///
21/// A process must be named before configuring output streams. This keeps public errors and tracing
22/// fields intentional, while stdout and stderr stream configuration remains explicit at the spawn
23/// call site.
24///
25/// # Examples
26///
27/// ```no_run
28/// use tokio_process_tools::*;
29/// use tokio_process_tools::SpawnError;
30/// use tokio::process::Command;
31///
32/// # tokio_test::block_on(async {
33/// let process = Process::new(Command::new("cargo"))
34///     .name("test-runner")
35///     .stdout_and_stderr(|stream| {
36///         stream
37///             .broadcast()
38///             .lossy_without_backpressure()
39///             .no_replay()
40///             .read_chunk_size(DEFAULT_READ_CHUNK_SIZE)
41///             .max_buffered_chunks(DEFAULT_MAX_BUFFERED_CHUNKS)
42///     })
43///     .spawn()?;
44/// # Ok::<_, SpawnError>(())
45/// # });
46/// ```
47pub struct Process<
48    NameState = Unnamed,
49    StdoutConfig = Unset,
50    Stdout = Unset,
51    StderrConfig = Unset,
52    Stderr = Unset,
53> {
54    cmd: tokio::process::Command,
55    name_state: NameState,
56    stdout_config: StdoutConfig,
57    stderr_config: StderrConfig,
58    _streams: PhantomData<fn() -> (Stdout, Stderr)>,
59}
60
61impl Process {
62    /// Creates a new process builder from a tokio command.
63    #[must_use]
64    pub fn new(cmd: tokio::process::Command) -> Self {
65        Self {
66            cmd,
67            name_state: Unnamed,
68            stdout_config: Unset,
69            stderr_config: Unset,
70            _streams: PhantomData,
71        }
72    }
73
74    /// Sets how the process should be named.
75    ///
76    /// You can provide either an explicit name or configure automatic name generation.
77    /// The name is used in public errors and tracing fields. By default, automatic
78    /// naming captures only the program name. Prefer `.name(...)` for stable safe
79    /// labels when command arguments or environment variables may contain secrets.
80    #[must_use]
81    pub fn name(self, name: impl Into<ProcessName>) -> Process<Named> {
82        Process {
83            cmd: self.cmd,
84            name_state: Named { name: name.into() },
85            stdout_config: Unset,
86            stderr_config: Unset,
87            _streams: PhantomData,
88        }
89    }
90}
91
92impl Process<Named> {
93    /// Configures stdout and stderr with the same output stream settings.
94    #[must_use]
95    pub fn stdout_and_stderr<Config, Stream>(
96        self,
97        configure: impl FnOnce(ProcessStreamBuilder) -> Config,
98    ) -> Process<Named, Config, Stream, Config, Stream>
99    where
100        Config: ProcessStreamConfig<Stream> + Copy,
101        Stream: OutputStream,
102    {
103        let config = configure(ProcessStreamBuilder);
104        Process {
105            cmd: self.cmd,
106            name_state: self.name_state,
107            stdout_config: config,
108            stderr_config: config,
109            _streams: PhantomData,
110        }
111    }
112
113    /// Configures stdout before configuring stderr.
114    #[must_use]
115    pub fn stdout<StdoutConfig, Stdout>(
116        self,
117        configure: impl FnOnce(ProcessStreamBuilder) -> StdoutConfig,
118    ) -> Process<Named, StdoutConfig, Stdout>
119    where
120        StdoutConfig: ProcessStreamConfig<Stdout>,
121        Stdout: OutputStream,
122    {
123        Process {
124            cmd: self.cmd,
125            name_state: self.name_state,
126            stdout_config: configure(ProcessStreamBuilder),
127            stderr_config: Unset,
128            _streams: PhantomData,
129        }
130    }
131
132    /// Configures stderr before configuring stdout.
133    #[must_use]
134    pub fn stderr<StderrConfig, Stderr>(
135        self,
136        configure: impl FnOnce(ProcessStreamBuilder) -> StderrConfig,
137    ) -> Process<Named, Unset, Unset, StderrConfig, Stderr>
138    where
139        StderrConfig: ProcessStreamConfig<Stderr>,
140        Stderr: OutputStream,
141    {
142        Process {
143            cmd: self.cmd,
144            name_state: self.name_state,
145            stdout_config: Unset,
146            stderr_config: configure(ProcessStreamBuilder),
147            _streams: PhantomData,
148        }
149    }
150}
151
152impl<StdoutConfig, Stdout> Process<Named, StdoutConfig, Stdout>
153where
154    Stdout: OutputStream,
155{
156    /// Configures stderr and completes the process builder.
157    #[must_use]
158    pub fn stderr<StderrConfig, Stderr>(
159        self,
160        configure: impl FnOnce(ProcessStreamBuilder) -> StderrConfig,
161    ) -> Process<Named, StdoutConfig, Stdout, StderrConfig, Stderr>
162    where
163        StdoutConfig: ProcessStreamConfig<Stdout>,
164        StderrConfig: ProcessStreamConfig<Stderr>,
165        Stderr: OutputStream,
166    {
167        Process {
168            cmd: self.cmd,
169            name_state: self.name_state,
170            stdout_config: self.stdout_config,
171            stderr_config: configure(ProcessStreamBuilder),
172            _streams: PhantomData,
173        }
174    }
175}
176
177impl<StderrConfig, Stderr> Process<Named, Unset, Unset, StderrConfig, Stderr>
178where
179    Stderr: OutputStream,
180{
181    /// Configures stdout and completes the process builder.
182    #[must_use]
183    pub fn stdout<StdoutConfig, Stdout>(
184        self,
185        configure: impl FnOnce(ProcessStreamBuilder) -> StdoutConfig,
186    ) -> Process<Named, StdoutConfig, Stdout, StderrConfig, Stderr>
187    where
188        StderrConfig: ProcessStreamConfig<Stderr>,
189        StdoutConfig: ProcessStreamConfig<Stdout>,
190        Stdout: OutputStream,
191    {
192        Process {
193            cmd: self.cmd,
194            name_state: self.name_state,
195            stdout_config: configure(ProcessStreamBuilder),
196            stderr_config: self.stderr_config,
197            _streams: PhantomData,
198        }
199    }
200}
201
202impl<StdoutConfig, Stdout, StderrConfig, Stderr>
203    Process<Named, StdoutConfig, Stdout, StderrConfig, Stderr>
204where
205    Stdout: OutputStream,
206    Stderr: OutputStream,
207{
208    /// Spawns the process with the configured output streams.
209    ///
210    /// # Errors
211    ///
212    /// Returns [`SpawnError::SpawnFailed`] if the process cannot be spawned.
213    pub fn spawn(self) -> Result<ProcessHandle<Stdout, Stderr>, SpawnError>
214    where
215        StdoutConfig: ProcessStreamConfig<Stdout>,
216        StderrConfig: ProcessStreamConfig<Stderr>,
217    {
218        let name = generate_name(&self.name_state.name, &self.cmd);
219        ProcessHandle::<Stdout, Stderr>::spawn_with_stream_configs(
220            name,
221            self.cmd,
222            self.stdout_config,
223            self.stderr_config,
224        )
225    }
226}