use std::fs;
use std::path::Path;
use anyhow::{Context, Result, bail};
use crate::{generator, templates};
pub const DIR: &str = "gateway";
pub const LEGACY_BIN: &str = "src/bin/gateway.rs";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Layout {
Package,
Legacy,
}
pub fn layout(root: &Path) -> Option<Layout> {
if root.join(DIR).join("Cargo.toml").is_file() {
Some(Layout::Package)
} else if root.join(LEGACY_BIN).is_file() {
Some(Layout::Legacy)
} else {
None
}
}
pub fn is_installed(root: &Path) -> bool {
layout(root).is_some()
}
pub fn install(root: &Path) -> Result<bool> {
if is_installed(root) {
return Ok(false);
}
let manifest_path = root.join("Cargo.toml");
let manifest = fs::read_to_string(&manifest_path)
.with_context(|| format!("failed to read {}", manifest_path.display()))?;
let manifest = add_to_workspace(&manifest)?;
for file in templates::GATEWAY {
generator::write_file(&root.join(DIR).join(file.path), file.contents, false)?;
}
fs::write(&manifest_path, manifest)
.with_context(|| format!("failed to update {}", manifest_path.display()))?;
println!(" installed the gateway ({DIR}/, a workspace package using slave-gateway)");
Ok(true)
}
fn add_to_workspace(manifest: &str) -> Result<String> {
let mut lines: Vec<String> = manifest.lines().map(str::to_owned).collect();
let mut section = String::new();
let mut members = None;
let mut framework = None;
for (index, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with('[') {
section = trimmed.to_owned();
} else if section == "[workspace]" && trimmed.starts_with("members") {
members = Some(index);
} else if section == "[workspace.dependencies]" && trimmed.starts_with("slave-framework") {
framework = Some(index);
}
}
let (Some(members), Some(framework)) = (members, framework) else {
bail!(
"could not find `members` in [workspace] and slave-framework in [workspace.dependencies] \
of Cargo.toml; add `\"{DIR}\"` to the members and `slave-gateway = \"{}\"` yourself",
templates::GATEWAY_VERSION
);
};
let dependency = gateway_dependency(&lines[framework])?;
lines.insert(framework + 1, dependency);
let line = &lines[members];
let Some(close) = line.rfind(']') else {
bail!("cannot parse the workspace `members` line: {line}");
};
let (head, tail) = line.split_at(close);
let separator = if head.trim_end().ends_with('[') {
""
} else {
", "
};
lines[members] = format!("{}{separator}\"{DIR}\"{tail}", head.trim_end());
Ok(lines.join("\n") + "\n")
}
fn gateway_dependency(framework_line: &str) -> Result<String> {
let local = framework_line
.split_once("path = \"")
.and_then(|(_, rest)| rest.split_once('"'))
.map(|(path, _)| Path::new(path).with_file_name("gateway"))
.filter(|path| path.join("Cargo.toml").is_file());
match local {
Some(path) => {
let path = crate::commands::new::local_crate(&path, "slave-gateway")?;
Ok(format!("slave-gateway = {{ path = \"{path}\" }}"))
}
None => Ok(format!(
"slave-gateway = \"{}\"",
templates::GATEWAY_VERSION
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_the_package_to_the_workspace() {
let manifest = "[package]\nname = \"app\"\n\n[dependencies]\naxum = \"0.8\"\n\n\
[workspace]\nmembers = [\"apps/*\"]\n\n[workspace.dependencies]\n\
slave-framework = { version = \"0.7\", default-features = false }\n";
let updated = add_to_workspace(manifest).unwrap();
assert!(
updated.contains("members = [\"apps/*\", \"gateway\"]"),
"{updated}"
);
assert!(
updated.ends_with(&format!(
"default-features = false }}\nslave-gateway = \"{}\"\n",
templates::GATEWAY_VERSION
)),
"{updated}"
);
assert!(
!updated.contains("slave-gateway.workspace"),
"backend does not depend on it"
);
assert!(add_to_workspace("[package]\nname = \"x\"\n").is_err());
}
}