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