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
22#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
23pub struct LaunchSpec {
24 pub argv: Vec<String>,
25 pub env: Vec<LaunchEnv>,
26 pub cwd: PathBuf,
27}
28
29impl LaunchSpec {
30 pub fn command(&self) -> Result<&str, LauncherError> {
31 self.argv
32 .first()
33 .map(String::as_str)
34 .ok_or(LauncherError::EmptyArgv)
35 }
36}
37
38pub trait RuntimeLauncher: Sync {
39 fn kind(&self) -> RuntimeKind;
40
41 fn argv(&self, request: &SpawnRequest) -> Result<Vec<String>, LauncherError>;
42
43 fn env(&self, request: &SpawnRequest) -> Result<Vec<LaunchEnv>, LauncherError>;
44
45 fn cwd(&self, request: &SpawnRequest) -> Result<PathBuf, LauncherError> {
46 Ok(request.cwd.clone())
47 }
48
49 fn launch_spec(&self, request: &SpawnRequest) -> Result<LaunchSpec, LauncherError> {
50 let spec = LaunchSpec {
51 argv: self.argv(request)?,
52 env: self.env(request)?,
53 cwd: self.cwd(request)?,
54 };
55 if spec.argv.is_empty() {
56 return Err(LauncherError::EmptyArgv);
57 }
58 if spec.env.is_empty() {
59 return Err(LauncherError::EmptyEnv {
60 runtime_kind: self.kind().to_string(),
61 });
62 }
63 Ok(spec)
64 }
65}
66
67#[derive(Clone, Debug, thiserror::Error)]
68pub enum LauncherError {
69 #[error("no launcher registered for runtime kind: {runtime_kind}")]
70 NoLauncher { runtime_kind: String },
71 #[error("launcher produced empty argv")]
72 EmptyArgv,
73 #[error("launcher {runtime_kind} produced empty env")]
74 EmptyEnv { runtime_kind: String },
75 #[error("failed to resolve launcher binary {binary}: {message}")]
76 BinaryLookupFailed { binary: String, message: String },
77}