workspacer_crate/
is_private.rs

1// ---------------- [ File: workspacer-crate/src/is_private.rs ]
2crate::ix!();
3
4#[async_trait]
5impl IsPrivate for CrateHandle
6{
7    type Error = CrateError;
8    /// Checks if the crate is private by reading the 'publish' field
9    /// or 'publish = false' or 'package.publish = false' in Cargo.toml.
10    /// Returns `Ok(true)` if private, `Ok(false)` if not private.
11    async fn is_private(&self) -> Result<bool, Self::Error>
12    {
13        let cargo_toml = self.cargo_toml();
14
15        let cargo_toml_guard = cargo_toml.lock().await;
16
17        let pkg_section = cargo_toml_guard.get_package_section()?;
18
19        // The crate might specify "publish = false", or an array of allowed registries.
20        // We'll say "private" if there's an explicit false or if "publish" is missing altogether
21        // but typically "private" is recognized if "publish" = false in the package section.
22        if let Some(publish_val) = pkg_section.get("publish") {
23            // Could be boolean or array
24            match publish_val {
25                toml::Value::Boolean(b) => {
26                    if !b {
27                        return Ok(true);
28                    }
29                }
30                // If there's an array of registries, we consider it public enough
31                // for crates.io if "crates-io" is in that array or if it's empty, etc.
32                toml::Value::Array(_) => {
33                    // That might be considered public, so we skip marking it private
34                }
35                _ => {}
36            }
37        }
38
39        // Check for "package.private" if it exists (rare in old cargo, but let's consider it).
40        if let Some(private_val) = pkg_section.get("private").and_then(|val| val.as_bool()) {
41            if private_val {
42                return Ok(true);
43            }
44        }
45        Ok(false)
46    }
47}
48
49// Demonstrates testing `is_private()` using the **real** `CrateHandle` and a
50// temporary directory, without introducing any mock types.
51#[cfg(test)]
52mod test_is_private {
53    use super::*;
54
55    // ---------------- [ File: tests/test_is_private_no_mocks.rs ]
56    // Demonstrates testing `is_private()` on the **real** `CrateHandle` by
57    // writing various Cargo.toml files in a temporary directory, with no mocks.
58
59    // Remove or comment out the *blanket impl* that conflicts, e.g.:
60    //
61    // #[async_trait::async_trait]
62    // impl<P> HasCargoTomlPathBuf for P
63    // where
64    //     for<'async_trait> P: AsRef<Path> + Send + Sync + 'async_trait,
65    // {
66    //     type Error = CrateError;
67    //     async fn cargo_toml_path_buf(&self) -> Result<PathBuf, Self::Error> {
68    //         ...
69    //     }
70    // }
71    //
72    // Instead, we define our own LocalCratePath type & impl:
73
74    /// 1) A local newtype around `PathBuf`, so we can implement
75    ///    `HasCargoTomlPathBuf` for it without hitting the orphan rule.
76    #[derive(Debug, Clone)]
77    pub struct LocalCratePath(pub PathBuf);
78
79    impl AsRef<Path> for LocalCratePath {
80        fn as_ref(&self) -> &Path {
81            self.0.as_ref()
82        }
83    }
84
85    // 4) Now we can test is_private() by constructing real CrateHandles
86    //    from a LocalCratePath. We do not need any mock handles.
87    //
88
89    #[tokio::test]
90    async fn test_publish_false_means_private() -> Result<(), CrateError> {
91        let tmp_dir = tempdir()?;
92        let root_path = tmp_dir.path().to_path_buf();
93
94        // Make sure to use the tokio async create_dir_all:
95        tokio::fs::create_dir_all(&root_path).await?;
96
97        let cargo_toml_contents = r#"
98            [package]
99            name = "private_crate"
100            version = "0.1.0"
101            authors = ["Someone <someone@example.com>"]
102            license = "MIT"
103            publish = false
104        "#;
105
106        let cargo_path = root_path.join("Cargo.toml");
107        let mut file = File::create(&cargo_path).await?;
108        file.write_all(cargo_toml_contents.as_bytes()).await?;
109
110        // 5) We explicitly specify the type param <LocalCratePath> so the compiler
111        //    knows which impl to use for `AsyncTryFrom<P>`.
112        //    Or we could do: `let handle: CrateHandle = CrateHandle::new(&LocalCratePath(root_path)).await?;`
113        let handle = CrateHandle::new(&LocalCratePath(root_path)).await?;
114
115        let is_priv = handle.is_private().await?;
116        assert!(is_priv, "Expected is_private() to be true when publish=false");
117        Ok(())
118    }
119
120    #[tokio::test]
121    async fn test_no_publish_defaults_to_false() -> Result<(), CrateError> {
122        let tmp_dir = tempdir()?;
123        let root_path = tmp_dir.path().to_path_buf();
124        tokio::fs::create_dir_all(&root_path).await?;
125
126        let cargo_toml_contents = r#"
127            [package]
128            name = "no_publish_crate"
129            version = "0.1.0"
130            authors = ["NoOne"]
131            license = "MIT"
132            # no publish field
133        "#;
134
135        let cargo_path = root_path.join("Cargo.toml");
136        let mut file = File::create(&cargo_path).await?;
137        file.write_all(cargo_toml_contents.as_bytes()).await?;
138
139        let handle: CrateHandle = CrateHandle::new(&LocalCratePath(root_path)).await?;
140        let is_priv = handle.is_private().await?;
141        assert!(!is_priv, "No publish field => not private");
142        Ok(())
143    }
144
145    #[tokio::test]
146    async fn test_publish_true_means_public() -> Result<(), CrateError> {
147        let tmp_dir = tempdir()?;
148        let root_path = tmp_dir.path().to_path_buf();
149        tokio::fs::create_dir_all(&root_path).await?;
150
151        let cargo_toml_contents = r#"
152            [package]
153            name = "public_crate"
154            version = "0.1.0"
155            authors = ["Public <public@example.com>"]
156            license = "MIT"
157            publish = true
158        "#;
159
160        let cargo_path = root_path.join("Cargo.toml");
161        let mut file = File::create(&cargo_path).await?;
162        file.write_all(cargo_toml_contents.as_bytes()).await?;
163
164        let handle = CrateHandle::new(&LocalCratePath(root_path)).await?;
165        assert!(!handle.is_private().await?, "publish=true => not private");
166        Ok(())
167    }
168
169    #[tokio::test]
170    async fn test_publish_array_is_public() -> Result<(), CrateError> {
171        let tmp_dir = tempdir()?;
172        let root_path = tmp_dir.path().to_path_buf();
173        tokio::fs::create_dir_all(&root_path).await?;
174
175        let cargo_toml_contents = r#"
176            [package]
177            name = "array_publish"
178            version = "0.1.0"
179            authors = ["Arr <arr@example.com>"]
180            license = "MIT"
181            publish = ["custom-registry", "crates-io"]
182        "#;
183
184        let cargo_path = root_path.join("Cargo.toml");
185        let mut file = File::create(&cargo_path).await?;
186        file.write_all(cargo_toml_contents.as_bytes()).await?;
187
188        let handle = CrateHandle::new(&LocalCratePath(root_path)).await?;
189        assert!(!handle.is_private().await?, "Array publish => not private");
190        Ok(())
191    }
192
193    #[tokio::test]
194    async fn test_private_true() -> Result<(), CrateError> {
195        let tmp_dir = tempdir()?;
196        let root_path = tmp_dir.path().to_path_buf();
197        tokio::fs::create_dir_all(&root_path).await?;
198
199        let cargo_toml_contents = r#"
200            [package]
201            name = "old_style_private"
202            version = "0.1.0"
203            authors = ["Legacy <legacy@example.com>"]
204            license = "MIT"
205            private = true
206        "#;
207
208        let cargo_path = root_path.join("Cargo.toml");
209        let mut file = File::create(&cargo_path).await?;
210        file.write_all(cargo_toml_contents.as_bytes()).await?;
211
212        let handle = CrateHandle::new(&LocalCratePath(root_path)).await?;
213        assert!(handle.is_private().await?, "`private=true` => is_private=true");
214        Ok(())
215    }
216
217    #[tokio::test]
218    async fn test_conflicting_publish_false_and_private_false() -> Result<(), CrateError> {
219        let tmp_dir = tempdir()?;
220        let root_path = tmp_dir.path().to_path_buf();
221        tokio::fs::create_dir_all(&root_path).await?;
222
223        let cargo_toml_contents = r#"
224            [package]
225            name = "conflicting_fields"
226            version = "0.1.0"
227            authors = ["Conflicts <conf@example.com>"]
228            license = "MIT"
229            publish = false
230            private = false
231        "#;
232
233        let cargo_path = root_path.join("Cargo.toml");
234        let mut file = File::create(&cargo_path).await?;
235        file.write_all(cargo_toml_contents.as_bytes()).await?;
236
237        let handle = CrateHandle::new(&LocalCratePath(root_path)).await?;
238        assert!(handle.is_private().await?, "publish=false overrides private=false => private");
239        Ok(())
240    }
241
242    #[tokio::test]
243    async fn test_publish_is_string() -> Result<(), CrateError> {
244        let tmp_dir = tempdir()?;
245        let root_path = tmp_dir.path().to_path_buf();
246        tokio::fs::create_dir_all(&root_path).await?;
247
248        let cargo_toml_contents = r#"
249            [package]
250            name = "str_publish"
251            version = "0.1.0"
252            authors = ["String <str@example.com>"]
253            license = "MIT"
254            publish = "any_string"
255        "#;
256
257        let cargo_path = root_path.join("Cargo.toml");
258        let mut file = File::create(&cargo_path).await?;
259        file.write_all(cargo_toml_contents.as_bytes()).await?;
260
261        let handle = CrateHandle::new(&LocalCratePath(root_path)).await?;
262        assert!(!handle.is_private().await?, "publish = \"string\" => not private");
263        Ok(())
264    }
265}