Skip to main content

muster/domain/config/
spec.rs

1use std::path::PathBuf;
2
3use getset::{Getters, WithSetters};
4use serde::{Deserialize, Serialize};
5use typed_builder::TypedBuilder;
6
7use crate::domain::{
8    process::{AgentTool, Process, ProcessKind, RestartPolicy, StopPolicy},
9    value::{CommandLine, Description, PaneId, ProcessName},
10};
11
12/// Declarative definition of one process within a workspace section.
13#[derive(Clone, Debug, Serialize, Deserialize, Getters, WithSetters, TypedBuilder)]
14#[set_with]
15pub struct ProcessSpec {
16    #[getset(get = "pub", set_with = "pub")]
17    name: ProcessName,
18    #[getset(get = "pub", set_with = "pub")]
19    #[builder(default)]
20    command: Option<CommandLine>,
21    #[getset(get = "pub", set_with = "pub")]
22    #[builder(default)]
23    working_dir: Option<PathBuf>,
24    #[getset(get = "pub", set_with = "pub")]
25    #[builder(default)]
26    description: Option<Description>,
27    #[getset(get = "pub", set_with = "pub")]
28    #[builder(default)]
29    restart: Option<RestartPolicy>,
30    /// Optional graceful shutdown policy. Only command specs may configure it.
31    #[getset(get = "pub", set_with = "pub")]
32    #[builder(default)]
33    stop: Option<StopPolicy>,
34    /// Whether to launch this process automatically on load. Absent means the
35    /// per-kind default; `true`/`false` overrides it.
36    #[getset(get = "pub", set_with = "pub")]
37    #[builder(default)]
38    autostart: Option<bool>,
39}
40
41impl ProcessSpec {
42    /// Builds the corresponding `Process` entity in its initial `Pending` state.
43    pub fn to_process(&self, id: PaneId, kind: ProcessKind) -> Process {
44        Process::builder()
45            .id(id)
46            .name(self.name.clone())
47            .kind(kind)
48            .agent_tool(
49                (kind == ProcessKind::Agent)
50                    .then(|| AgentTool::from_command(self.command.as_ref())),
51            )
52            .command(self.command.clone())
53            .working_dir(self.working_dir.clone())
54            .description(self.description.clone())
55            .restart(self.restart_policy())
56            .stop(if kind == ProcessKind::Command {
57                self.stop.clone()
58            } else {
59                None
60            })
61            .autostart(self.should_autostart(kind))
62            .build()
63    }
64
65    /// The effective restart policy, treating an absent policy as `Never`.
66    pub fn restart_policy(&self) -> RestartPolicy {
67        self.restart.unwrap_or(RestartPolicy::Never)
68    }
69
70    /// Whether this process auto-starts. Defaults to false for commands (which
71    /// wait for an explicit start) and true for agents and terminals.
72    pub fn should_autostart(&self, kind: ProcessKind) -> bool {
73        self.autostart.unwrap_or(kind != ProcessKind::Command)
74    }
75
76    /// The effective graceful shutdown policy for `kind`. Commands use the
77    /// domain default when the config omits `stop`; other kinds have no policy.
78    pub fn effective_stop_policy(&self, kind: ProcessKind) -> Option<StopPolicy> {
79        if kind == ProcessKind::Command {
80            Some(self.stop.clone().unwrap_or_default())
81        } else {
82            None
83        }
84    }
85}