workspacer_bump/
bump_crate.rs

1// ---------------- [ File: workspacer-bump/src/bump_crate.rs ]
2crate::ix!();
3
4#[async_trait]
5impl<T> Bump for T
6where
7    T: ValidateIntegrity<Error = CrateError>
8        + HasCargoToml
9        + HasCargoTomlPathBuf<Error = CrateError>
10        + AsRef<Path>
11        + Send
12        + Sync,
13{
14    type Error = CrateError;
15
16    async fn bump(&mut self, release: ReleaseType) -> Result<(), Self::Error> {
17        trace!("Entered Bump::bump with release={:?}", release);
18
19        // A) Lock briefly just to *read* the old version
20        let old_version_str = {
21            let cargo_toml_arc = self.cargo_toml();
22            let guard = cargo_toml_arc.lock().await;
23
24            // read old version as a string, BUT unify missing file => IoError
25            let ver = match guard.version() {
26                Err(CargoTomlError::FileNotFound { missing_file }) => {
27                    error!("bump(): mapping 'FileNotFound' to CrateError::IoError");
28                    return Err(CrateError::IoError {
29                        io_error: Arc::new(std::io::Error::new(
30                            std::io::ErrorKind::NotFound,
31                            format!("Cargo.toml not found at {}", missing_file.display())
32                        )),
33                        context: format!("Cannot read Cargo.toml at {:?}", missing_file),
34                    });
35                }
36                Err(e) => {
37                    error!("bump(): got CargoTomlError => returning CrateError::CargoTomlError({:?})", e);
38                    return Err(CrateError::CargoTomlError(e));
39                }
40                Ok(ver_ok) => ver_ok,
41            };
42            let ver_str = ver.to_string();
43            debug!("Current version (before bump) is '{}', about to do integrity check", ver_str);
44            ver_str
45        };
46
47        // B) Now do sabotage check and forced re-parse from disk
48        trace!("Validating crate integrity before proceeding with bump");
49        self.validate_integrity().await?;
50
51        // C) Re-lock to do the actual bump patch
52        let cargo_toml_path;
53        let new_version_str;
54        {
55            let cargo_toml_arc = self.cargo_toml();
56            let mut guard = cargo_toml_arc.lock().await;
57
58            // parse the old version again (fresh)
59            let mut old_ver = match guard.version() {
60                Err(CargoTomlError::FileNotFound { missing_file }) => {
61                    error!("bump(): second read => mapping 'FileNotFound' to IoError");
62                    return Err(CrateError::IoError {
63                        io_error: Arc::new(std::io::Error::new(
64                            std::io::ErrorKind::NotFound,
65                            format!("Cargo.toml not found at {}", missing_file.display())
66                        )),
67                        context: format!("Cannot re-read Cargo.toml at {:?}", missing_file),
68                    });
69                }
70                Err(e) => {
71                    error!("bump(): second read => CargoTomlError => returning CrateError::CargoTomlError({:?})", e);
72                    return Err(CrateError::CargoTomlError(e));
73                }
74                Ok(ver_ok) => ver_ok,
75            };
76            let old_clone = old_ver.clone();
77
78            // apply the release
79            release.apply_to(&mut old_ver);
80
81            // preserve the old build metadata
82            old_ver.build = old_clone.build.clone();
83
84            // Overwrite in-memory
85            let bumped = old_ver.to_string();
86            {
87                let pkg = guard.get_package_section_mut()?;
88                // same code as before for setting the version in the table
89                if let Some(tbl) = pkg.as_table_mut() {
90                    tbl.insert("version".to_owned(), toml::Value::String(bumped.clone()));
91                    debug!("Set `package.version` to '{}'", bumped);
92                } else {
93                    error!("`package` section was not a TOML table; cannot set version!");
94                    return Err(CrateError::CouldNotSetPackageVersionBecausePackageIsNotATable);
95                }
96            }
97
98            cargo_toml_path = self.as_ref().join("Cargo.toml");
99            new_version_str = bumped;
100        } // drop lock
101
102        // D) Finally save to disk outside the lock
103        {
104            let cargo_toml_arc = self.cargo_toml();
105            let guard = cargo_toml_arc.lock().await;
106            guard.save_to_disk().await?;
107        }
108
109        info!(
110            "Successfully bumped crate at {:?} from {} to {}",
111            cargo_toml_path, old_version_str, new_version_str
112        );
113        Ok(())
114    }
115}
116
117#[cfg(test)]
118mod test_bump_crate_handle_with_mock {
119    use super::*;
120
121    #[traced_test]
122    async fn test_bump_patch_ok_with_mock() {
123        // 1) Create a fully valid mock crate that simulates everything present
124        let mock_crate = MockCrateHandle::fully_valid_config()
125            .to_builder()
126            .crate_name("patch_ok") // name is optional, but helpful for logs
127            .build()
128            .unwrap();
129
130        // 2) Wrap it in Arc<AsyncMutex> so it implements CrateHandleInterface in your async code
131        let arc_handle = Arc::new(AsyncMutex::new(mock_crate));
132
133        // 3) Perform the patch bump
134        {
135            let mut guard = arc_handle.lock().await;
136            guard.bump(ReleaseType::Patch).await
137                 .expect("Expected patch bump to succeed in a fully-valid mock");
138        }
139
140        // 4) Confirm the new version in-memory (no real Cargo.toml on disk!)
141        {
142            let guard = arc_handle.lock().await;
143            let new_ver = guard.version().expect("Should parse bumped version from mock");
144            assert_eq!(new_ver.to_string(), "1.2.4", "Mock crate's version should become 1.2.4");
145        }
146    }
147
148    #[traced_test]
149    async fn test_bump_fails_if_readme_missing() {
150        // 1) Create a mock crate that simulates a missing README.md
151        let mock_crate = MockCrateHandle::missing_readme_config();
152
153        let arc_handle = Arc::new(AsyncMutex::new(mock_crate));
154
155        // 2) Attempt to bump => should fail because integrity check sees no README
156        let bump_result = {
157            let mut guard = arc_handle.lock().await;
158            guard.bump(ReleaseType::Minor).await
159        };
160
161        // 3) Confirm it fails with the expected error
162        match bump_result {
163            Err(crate_error) => {
164                println!("Got expected error: {:?}", crate_error);
165                assert!(format!("{:?}", crate_error).contains("README.md"),
166                    "Error should mention missing README file");
167            }
168            Ok(_) => panic!("Expected missing README to cause a bump failure"),
169        }
170    }
171}