1use super::*;
2
3fn read_workspace_version(doc: &Value) -> Result<String, SyncError> {
14 let version: String = doc
15 .get("workspace")
16 .and_then(|workspace: &Value| workspace.get("package"))
17 .and_then(|package: &Value| package.get("version"))
18 .and_then(|version_value: &Value| version_value.as_str())
19 .ok_or_else(|| SyncError::WorkspaceVersionMissing("Cargo.toml".to_string()))?
20 .to_string();
21 Ok(version)
22}
23
24fn read_workspace_members(doc: &Value) -> Result<Vec<String>, SyncError> {
34 let members: Vec<String> = doc
35 .get("workspace")
36 .and_then(|workspace: &Value| workspace.get("members"))
37 .and_then(|members_value: &Value| members_value.as_array())
38 .ok_or_else(|| SyncError::WorkspaceMembersMissing("Cargo.toml".to_string()))?
39 .iter()
40 .filter_map(|member: &Value| member.as_str().map(|s: &str| s.to_string()))
41 .collect();
42 Ok(members)
43}
44
45fn read_member_crate_name(doc: &Value) -> Result<String, SyncError> {
55 let name: String = doc
56 .get("package")
57 .and_then(|package: &Value| package.get("name"))
58 .and_then(|name_value: &Value| name_value.as_str())
59 .ok_or_else(|| SyncError::MemberNameMissing("Cargo.toml".to_string()))?
60 .to_string();
61 Ok(name)
62}
63
64fn find_dep_alias_for_member_path(deps: &Value, member_path: &str) -> Option<String> {
76 let table: &toml::map::Map<String, Value> = deps.as_table()?;
77 for (alias, entry) in table {
78 let entry_table: &toml::map::Map<String, Value> = entry.as_table()?;
79 let path: &Value = entry_table.get("path")?;
80 if path.as_str()? == member_path {
81 return Some(alias.clone());
82 }
83 }
84 None
85}
86
87fn rewrite_dep_entry(
106 deps: &mut toml::map::Map<String, Value>,
107 member_path: &str,
108 current_alias: &str,
109 canonical_alias: &str,
110 workspace_version: &str,
111) -> Option<(String, String)> {
112 let entry: &mut Value = deps.get_mut(current_alias)?;
113 let entry_table: &mut toml::map::Map<String, Value> = entry.as_table_mut()?;
114 let path_matches: bool = entry_table
115 .get("path")
116 .and_then(|path_value: &Value| path_value.as_str())
117 .map(|s: &str| s == member_path)
118 .unwrap_or(false);
119 if !path_matches {
120 return None;
121 }
122 entry_table.insert(
123 "version".to_string(),
124 Value::String(workspace_version.to_string()),
125 );
126 if current_alias != canonical_alias {
127 let renamed: (String, String) = (current_alias.to_string(), canonical_alias.to_string());
128 let new_entry: Value = entry.clone();
129 deps.remove(current_alias);
130 let entry_to_set: &mut Value = deps.entry(canonical_alias.to_string()).or_insert(new_entry);
131 let canonical_table: &mut toml::map::Map<String, Value> =
132 entry_to_set.as_table_mut().unwrap();
133 canonical_table.insert("path".to_string(), Value::String(member_path.to_string()));
134 canonical_table.insert(
135 "version".to_string(),
136 Value::String(workspace_version.to_string()),
137 );
138 Some(renamed)
139 } else {
140 None
141 }
142}
143
144pub async fn execute_sync(manifest_path: &str) -> Result<SyncReport, SyncError> {
173 let path: &Path = Path::new(manifest_path);
174 let content: String = read_to_string(path).await?;
175 let mut doc: Value = toml::from_str(&content).map_err(|_| SyncError::ManifestParseError)?;
176 let workspace_version: String = read_workspace_version(&doc)?;
177 let members: Vec<String> = read_workspace_members(&doc)?;
178 if members.is_empty() {
179 log::info!("sync: no workspace members, nothing to do");
180 return Ok(SyncReport {
181 workspace_version,
182 renamed_entries: Vec::new(),
183 versioned_entries: Vec::new(),
184 file_changed: false,
185 });
186 }
187 let mut renamed_entries: Vec<(String, String)> = Vec::new();
188 let mut versioned_entries: Vec<(String, String)> = Vec::new();
189 let mut needs_rewrite: bool = false;
190 let deps_value: Option<&Value> = doc
191 .get("workspace")
192 .and_then(|workspace: &Value| workspace.get("dependencies"));
193 let mut deps: toml::map::Map<String, Value> = deps_value
194 .and_then(|value: &Value| value.as_table())
195 .cloned()
196 .unwrap_or_default();
197 for member_path in &members {
198 let member_manifest_path: PathBuf = path
199 .parent()
200 .unwrap_or_else(|| Path::new("."))
201 .join(member_path)
202 .join("Cargo.toml");
203 if !member_manifest_path.exists() {
204 return Err(SyncError::MemberManifestMissing(
205 member_manifest_path.display().to_string(),
206 ));
207 }
208 let member_content: String = read_to_string(&member_manifest_path).await?;
209 let member_doc: Value =
210 toml::from_str(&member_content).map_err(|_| SyncError::ManifestParseError)?;
211 let canonical_alias: String = read_member_crate_name(&member_doc)?;
212 let current_alias: Option<String> =
213 find_dep_alias_for_member_path(&Value::Table(deps.clone()), member_path);
214 let current_alias: String = match current_alias {
215 Some(alias) => alias,
216 None => {
217 log::info!(
218 "sync: {} -> no [workspace.dependencies] entry, skipping",
219 member_path
220 );
221 continue;
222 }
223 };
224 let existing_version: Option<String> = deps
225 .get(¤t_alias)
226 .and_then(|entry: &Value| entry.as_table())
227 .and_then(|table: &toml::map::Map<String, Value>| table.get("version"))
228 .and_then(|version_value: &Value| version_value.as_str())
229 .map(|s: &str| s.to_string());
230 let alias_needs_rename: bool = current_alias != canonical_alias;
231 let version_needs_rewrite: bool = existing_version
232 .as_deref()
233 .map(|existing: &str| existing != workspace_version)
234 .unwrap_or(true);
235 if !alias_needs_rename && !version_needs_rewrite {
236 continue;
237 }
238 needs_rewrite = true;
239 let renamed: Option<(String, String)> = rewrite_dep_entry(
240 &mut deps,
241 member_path,
242 ¤t_alias,
243 &canonical_alias,
244 &workspace_version,
245 );
246 if let Some((old, new)) = &renamed {
247 log::info!("sync: {} renamed {} -> {}", member_path, old, new);
248 renamed_entries.push((old.clone(), new.clone()));
249 } else {
250 log::info!(
251 "sync: {} -> {} v{}",
252 member_path,
253 canonical_alias,
254 workspace_version
255 );
256 }
257 versioned_entries.push((member_path.clone(), canonical_alias));
258 }
259 let file_changed: bool = needs_rewrite;
260 if file_changed {
261 if let Some(workspace_table) = doc
262 .get_mut("workspace")
263 .and_then(|workspace: &mut Value| workspace.as_table_mut())
264 {
265 workspace_table.insert("dependencies".to_string(), Value::Table(deps.clone()));
266 }
267 let updated_content: String =
268 toml::to_string(&doc).map_err(|_| SyncError::ManifestSerializeError)?;
269 write(path, updated_content).await?;
270 log::info!(
271 "sync: wrote {} entries to v{}",
272 versioned_entries.len(),
273 workspace_version
274 );
275 } else {
276 log::info!("sync: already in sync: v{}", workspace_version);
277 }
278 Ok(SyncReport {
279 workspace_version,
280 renamed_entries,
281 versioned_entries,
282 file_changed,
283 })
284}