use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
pub struct ManifestExtras {
#[serde(default)]
pub dependencies: BTreeMap<String, ExtraDepSpec>,
#[serde(default)]
pub dev_dependencies: BTreeMap<String, ExtraDepSpec>,
}
impl ManifestExtras {
pub fn is_empty(&self) -> bool {
self.dependencies.is_empty() && self.dev_dependencies.is_empty()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum ExtraDepSpec {
Simple(String),
Detailed(toml::Table),
}
impl ExtraDepSpec {
pub fn version(&self) -> Option<&str> {
match self {
Self::Simple(v) => Some(v),
Self::Detailed(t) => t.get("version").and_then(|v| v.as_str()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_simple_form() {
let toml_src = r#"
[dependencies]
"tree-sitter" = "^0.25.0"
"#;
let extras: ManifestExtras = toml::from_str(toml_src).expect("deserializes");
assert_eq!(extras.dependencies.len(), 1);
let spec = &extras.dependencies["tree-sitter"];
assert_eq!(spec.version(), Some("^0.25.0"));
assert!(matches!(spec, ExtraDepSpec::Simple(_)));
}
#[test]
fn deserialize_detailed_form() {
let toml_src = r#"
[dev_dependencies]
tracing = { version = "0.1", features = ["log"] }
"#;
let extras: ManifestExtras = toml::from_str(toml_src).expect("deserializes");
let spec = &extras.dev_dependencies["tracing"];
assert_eq!(spec.version(), Some("0.1"));
if let ExtraDepSpec::Detailed(t) = spec {
assert!(t.get("features").is_some());
} else {
panic!("expected Detailed form, got {spec:?}");
}
}
#[test]
fn defaults_are_empty() {
let extras = ManifestExtras::default();
assert!(extras.is_empty());
}
#[test]
fn partial_tables_deserialize() {
let toml_src = r#"
[dev_dependencies]
vitest = "^3.0.0"
"#;
let extras: ManifestExtras = toml::from_str(toml_src).expect("deserializes");
assert!(extras.dependencies.is_empty());
assert_eq!(extras.dev_dependencies.len(), 1);
}
#[test]
fn round_trip_preserves_order_and_values() {
let toml_src = r#"
[dependencies]
"a-pkg" = "1.0"
"b-pkg" = { version = "2.0" }
[dev_dependencies]
"z-pkg" = "9.9"
"#;
let extras: ManifestExtras = toml::from_str(toml_src).expect("deserializes");
let names: Vec<&str> = extras.dependencies.keys().map(String::as_str).collect();
assert_eq!(names, vec!["a-pkg", "b-pkg"]);
assert_eq!(extras.dependencies["b-pkg"].version(), Some("2.0"));
}
}