use serde::Deserialize;
pub(crate) const GENERATED_LAUNCHER: &str = "launch.mjs";
#[derive(Debug, Clone, Deserialize)]
#[serde(try_from = "LauncherConfig")]
pub struct Launcher {
file: Option<String>,
bin: Option<String>,
fail_open: bool,
}
impl Launcher {
pub fn copied(file: impl Into<String>, bin: Option<String>) -> Self {
Self {
file: Some(file.into()),
bin,
fail_open: false,
}
}
pub fn generated(bin: Option<String>, fail_open: bool) -> Self {
Self {
file: None,
bin,
fail_open,
}
}
pub fn output(&self) -> &str {
self.file.as_deref().unwrap_or(GENERATED_LAUNCHER)
}
pub fn bin(&self) -> Option<&str> {
self.bin.as_deref()
}
pub fn is_generated(&self) -> bool {
self.file.is_none()
}
pub fn fail_open(&self) -> bool {
self.fail_open
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum LauncherConfig {
Path(String),
Table {
#[serde(default)]
file: Option<String>,
#[serde(default)]
bin: Option<String>,
#[serde(default)]
fail_open: Option<bool>,
#[serde(flatten)]
extra: serde_json::Map<String, serde_json::Value>,
},
}
impl TryFrom<LauncherConfig> for Launcher {
type Error = String;
fn try_from(config: LauncherConfig) -> Result<Self, Self::Error> {
match config {
LauncherConfig::Path(file) => Ok(Self::copied(file, None)),
LauncherConfig::Table {
file,
bin,
fail_open,
extra,
} => {
if let Some(unknown) = extra.keys().next() {
return Err(format!("unknown launcher field `{unknown}`"));
}
if file.is_some() && fail_open == Some(true) {
return Err(
"`fail_open` only applies to a generated launcher; omit `file` to generate one"
.to_owned(),
);
}
Ok(Self {
file,
bin,
fail_open: fail_open.unwrap_or(false),
})
}
}
}
}