1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{RuntimeKind, SpawnRequest};
6
7#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
8pub struct LaunchEnv {
9 pub key: String,
10 pub value: String,
11}
12
13impl LaunchEnv {
14 pub fn new(key: impl Into<String>, value: impl Into<String>) -> Self {
15 Self {
16 key: key.into(),
17 value: value.into(),
18 }
19 }
20}
21
22pub fn upsert_launch_env(env: &mut Vec<LaunchEnv>, next: LaunchEnv) {
23 if let Some(existing) = env.iter_mut().find(|entry| entry.key == next.key) {
24 *existing = next;
25 } else {
26 env.push(next);
27 }
28}
29
30#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31pub struct LaunchSpec {
32 pub argv: Vec<String>,
33 pub env: Vec<LaunchEnv>,
34 pub cwd: PathBuf,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub shell_resume: Option<ShellResume>,
37}
38
39impl LaunchSpec {
40 pub fn command(&self) -> Result<&str, LauncherError> {
41 self.argv
42 .first()
43 .map(String::as_str)
44 .ok_or(LauncherError::EmptyArgv)
45 }
46}
47
48#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
49pub struct ShellResume {
50 pub argv: Vec<String>,
51 pub env: Vec<LaunchEnv>,
52 pub cwd: PathBuf,
53}
54
55impl ShellResume {
56 pub fn command(&self) -> Result<&str, LauncherError> {
57 self.argv
58 .first()
59 .map(String::as_str)
60 .ok_or(LauncherError::EmptyShellArgv)
61 }
62}
63
64pub trait RuntimeLauncher: Sync {
65 fn kind(&self) -> RuntimeKind;
66
67 fn argv(&self, request: &SpawnRequest) -> Result<Vec<String>, LauncherError>;
68
69 fn env(&self, request: &SpawnRequest) -> Result<Vec<LaunchEnv>, LauncherError>;
70
71 fn cwd(&self, request: &SpawnRequest) -> Result<PathBuf, LauncherError> {
72 Ok(request.cwd.clone())
73 }
74
75 fn launch_spec(&self, request: &SpawnRequest) -> Result<LaunchSpec, LauncherError> {
76 let spec = LaunchSpec {
77 argv: self.argv(request)?,
78 env: self.env(request)?,
79 cwd: self.cwd(request)?,
80 shell_resume: request.shell_resume.clone(),
81 };
82 if spec.argv.is_empty() {
83 return Err(LauncherError::EmptyArgv);
84 }
85 if spec.env.is_empty() {
86 return Err(LauncherError::EmptyEnv {
87 runtime_kind: self.kind().to_string(),
88 });
89 }
90 Ok(spec)
91 }
92}
93
94#[derive(Clone, Debug, thiserror::Error)]
95pub enum LauncherError {
96 #[error("no launcher registered for runtime kind: {runtime_kind}")]
97 NoLauncher { runtime_kind: String },
98 #[error("launcher produced empty argv")]
99 EmptyArgv,
100 #[error("launcher produced empty shell resume argv")]
101 EmptyShellArgv,
102 #[error("launcher {runtime_kind} produced empty env")]
103 EmptyEnv { runtime_kind: String },
104 #[error("failed to resolve launcher binary {binary}: {message}")]
105 BinaryLookupFailed { binary: String, message: String },
106}