use std::error;
use std::path::{Path, PathBuf};
use toml::Value;
use crate::pal::Filesystem;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum DetectedPackage {
Package(String),
Workspace,
}
#[derive(Debug)]
pub(crate) struct WorkspaceContext {
pub(crate) absolute_target_path: PathBuf,
pub(crate) workspace_root: PathBuf,
}
#[cfg_attr(test, mutants::skip)]
pub(crate) fn detect_package(
context: &WorkspaceContext,
fs: &impl Filesystem,
) -> Result<DetectedPackage, Box<dyn error::Error>> {
let absolute_path = &context.absolute_target_path;
let workspace_root = &context.workspace_root;
let mut current_dir = if fs.is_file(absolute_path) {
absolute_path.parent().unwrap()
} else {
absolute_path
};
while current_dir.starts_with(workspace_root) {
if fs.cargo_toml_exists(current_dir) && current_dir != workspace_root {
return extract_package_name(current_dir, fs);
}
current_dir = match current_dir.parent() {
Some(parent) => parent,
None => break,
};
}
Ok(DetectedPackage::Workspace)
}
pub(crate) fn extract_package_name(
dir: &Path,
fs: &impl Filesystem,
) -> Result<DetectedPackage, Box<dyn error::Error>> {
let contents = fs.read_cargo_toml(dir)?;
let value: Value = toml::from_str(&contents)?;
if let Some(package_table) = value.get("package")
&& let Some(name) = package_table.get("name")
&& let Some(name_str) = name.as_str()
{
return Ok(DetectedPackage::Package(name_str.to_string()));
}
Err(format!(
"Could not find package name in {}/Cargo.toml",
dir.display()
)
.into())
}
#[cfg(all(test, not(miri)))]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::fs;
use super::*;
use crate::pal::FilesystemFacade;
#[test]
fn extract_package_name_double_quotes() {
let temp_dir = tempfile::tempdir().unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
r#"
[package]
name = "test-package"
version = "0.1.0"
"#,
)
.unwrap();
let fs = FilesystemFacade::target();
let result = extract_package_name(temp_dir.path(), &fs).unwrap();
assert_eq!(result, DetectedPackage::Package("test-package".to_string()));
}
#[test]
fn extract_package_name_single_quotes() {
let temp_dir = tempfile::tempdir().unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
r#"
[package]
name = 'test-package-single'
version = "0.1.0"
"#,
)
.unwrap();
let fs = FilesystemFacade::target();
let result = extract_package_name(temp_dir.path(), &fs).unwrap();
assert_eq!(
result,
DetectedPackage::Package("test-package-single".to_string())
);
}
#[test]
fn extract_package_name_with_comments_and_complex_toml() {
let temp_dir = tempfile::tempdir().unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
r#"
# This is a comment
[package]
# Package name
name = "complex-package"
version = "0.1.0"
authors = ["Test Author <test@example.com>"]
description = "A test package with complex TOML"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
"#,
)
.unwrap();
let fs = FilesystemFacade::target();
let result = extract_package_name(temp_dir.path(), &fs).unwrap();
assert_eq!(
result,
DetectedPackage::Package("complex-package".to_string())
);
}
#[test]
fn extract_package_name_missing() {
let temp_dir = tempfile::tempdir().unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
r#"
[package]
version = "0.1.0"
"#,
)
.unwrap();
let fs = FilesystemFacade::target();
extract_package_name(temp_dir.path(), &fs)
.expect_err("Expected an error when package name is missing");
}
#[test]
fn detected_package_equality() {
assert_eq!(
DetectedPackage::Package("test".to_string()),
DetectedPackage::Package("test".to_string())
);
assert_eq!(DetectedPackage::Workspace, DetectedPackage::Workspace);
assert_ne!(
DetectedPackage::Package("test".to_string()),
DetectedPackage::Workspace
);
}
#[test]
fn detect_package_with_invalid_toml() {
let temp_dir = tempfile::tempdir().unwrap();
let workspace_root = temp_dir.path();
fs::write(
workspace_root.join("Cargo.toml"),
r#"[workspace]
members = ["bad_package"]
"#,
)
.unwrap();
let bad_package = workspace_root.join("bad_package");
fs::create_dir_all(bad_package.join("src")).unwrap();
fs::write(
bad_package.join("Cargo.toml"),
r#"# Intentionally malformed TOML - missing closing bracket
[package
name = "bad_package"
version = "0.1.0"
"#,
)
.unwrap();
fs::write(bad_package.join("src/lib.rs"), "// test\n").unwrap();
let context = WorkspaceContext {
absolute_target_path: bad_package.join("src/lib.rs").canonicalize().unwrap(),
workspace_root: workspace_root.canonicalize().unwrap(),
};
let fs = FilesystemFacade::target();
let result = detect_package(&context, &fs);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("TOML") || error_msg.contains("parse"),
"Expected TOML parse error, got: {error_msg}"
);
}
}