Skip to main content

standard_version/
cargo.rs

1//! Cargo.toml version file engine.
2//!
3//! Implements [`VersionFile`] for Rust's `Cargo.toml` manifest, detecting and
4//! rewriting the `version` field inside the `[package]` section while
5//! preserving formatting.
6
7use crate::toml_helpers;
8use crate::version_file::{VersionFile, VersionFileError};
9
10/// TOML section header for `Cargo.toml`.
11const SECTION: &str = "[package]";
12
13/// Version file engine for `Cargo.toml`.
14#[derive(Debug, Clone, Copy)]
15pub struct CargoVersionFile;
16
17impl VersionFile for CargoVersionFile {
18    fn name(&self) -> &str {
19        "Cargo.toml"
20    }
21
22    fn filenames(&self) -> &[&str] {
23        &["Cargo.toml"]
24    }
25
26    fn detect(&self, content: &str) -> bool {
27        toml_helpers::detect_version_in_section(content, SECTION)
28    }
29
30    fn read_version(&self, content: &str) -> Option<String> {
31        toml_helpers::read_version_in_section(content, SECTION)
32    }
33
34    fn write_version(&self, content: &str, new_version: &str) -> Result<String, VersionFileError> {
35        toml_helpers::write_version_in_section(content, SECTION, new_version)
36    }
37}
38
39// ---------------------------------------------------------------------------
40// Tests
41// ---------------------------------------------------------------------------
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    const BASIC_TOML: &str = r#"[package]
48name = "my-crate"
49version = "0.1.0"
50edition = "2021"
51"#;
52
53    const MULTI_SECTION_TOML: &str = r#"[package]
54name = "my-crate"
55version = "0.1.0"
56
57[dependencies]
58foo = { version = "1.0" }
59"#;
60
61    // --- detect ---
62
63    #[test]
64    fn detect_with_package_version() {
65        assert!(CargoVersionFile.detect(BASIC_TOML));
66    }
67
68    #[test]
69    fn detect_without_package_section() {
70        let content = "[dependencies]\nfoo = \"1\"\n";
71        assert!(!CargoVersionFile.detect(content));
72    }
73
74    #[test]
75    fn detect_version_only_in_deps() {
76        let content = "[package]\nname = \"x\"\n\n[dependencies]\nfoo = { version = \"1\" }\n";
77        assert!(!CargoVersionFile.detect(content));
78    }
79
80    // --- read_version ---
81
82    #[test]
83    fn read_version_basic() {
84        assert_eq!(
85            CargoVersionFile.read_version(BASIC_TOML),
86            Some("0.1.0".to_string()),
87        );
88    }
89
90    #[test]
91    fn read_version_no_package() {
92        let content = "[dependencies]\nfoo = \"1\"\n";
93        assert_eq!(CargoVersionFile.read_version(content), None);
94    }
95
96    // --- write_version ---
97
98    #[test]
99    fn write_version_basic() {
100        let result = CargoVersionFile.write_version(BASIC_TOML, "1.0.0").unwrap();
101        assert!(result.contains("version = \"1.0.0\""));
102        assert!(result.contains("name = \"my-crate\""));
103        assert!(result.contains("edition = \"2021\""));
104    }
105
106    #[test]
107    fn write_version_only_in_package_section() {
108        let result = CargoVersionFile
109            .write_version(MULTI_SECTION_TOML, "2.0.0")
110            .unwrap();
111        assert!(result.contains("version = \"2.0.0\""));
112        // Dependency version untouched.
113        assert!(result.contains("foo = { version = \"1.0\" }"));
114    }
115
116    #[test]
117    fn write_version_no_field_returns_error() {
118        let content = "[package]\nname = \"x\"\n";
119        let err = CargoVersionFile.write_version(content, "1.0.0");
120        assert!(err.is_err());
121    }
122
123    #[test]
124    fn write_version_preserves_no_trailing_newline() {
125        let content = "[package]\nname = \"x\"\nversion = \"0.1.0\"";
126        let result = CargoVersionFile.write_version(content, "0.2.0").unwrap();
127        assert!(!result.ends_with('\n'));
128        assert!(result.contains("version = \"0.2.0\""));
129    }
130}