use super::*;
fn read_workspace_version(doc: &Value) -> Result<String, SyncError> {
let version: String = doc
.get("workspace")
.and_then(|workspace: &Value| workspace.get("package"))
.and_then(|package: &Value| package.get("version"))
.and_then(|version_value: &Value| version_value.as_str())
.ok_or_else(|| SyncError::WorkspaceVersionMissing("Cargo.toml".to_string()))?
.to_string();
Ok(version)
}
fn read_workspace_members(doc: &Value) -> Result<Vec<String>, SyncError> {
let members: Vec<String> = doc
.get("workspace")
.and_then(|workspace: &Value| workspace.get("members"))
.and_then(|members_value: &Value| members_value.as_array())
.ok_or_else(|| SyncError::WorkspaceMembersMissing("Cargo.toml".to_string()))?
.iter()
.filter_map(|member: &Value| member.as_str().map(|s: &str| s.to_string()))
.collect();
Ok(members)
}
fn read_member_crate_name(doc: &Value) -> Result<String, SyncError> {
let name: String = doc
.get("package")
.and_then(|package: &Value| package.get("name"))
.and_then(|name_value: &Value| name_value.as_str())
.ok_or_else(|| SyncError::MemberNameMissing("Cargo.toml".to_string()))?
.to_string();
Ok(name)
}
fn find_dep_alias_for_member_path(deps: &Value, member_path: &str) -> Option<String> {
let table: &toml::map::Map<String, Value> = deps.as_table()?;
for (alias, entry) in table {
let entry_table: &toml::map::Map<String, Value> = entry.as_table()?;
let path: &Value = entry_table.get("path")?;
if path.as_str()? == member_path {
return Some(alias.clone());
}
}
None
}
fn rewrite_dep_entry(
deps: &mut toml::map::Map<String, Value>,
member_path: &str,
current_alias: &str,
canonical_alias: &str,
workspace_version: &str,
) -> Option<(String, String)> {
let entry: &mut Value = deps.get_mut(current_alias)?;
let entry_table: &mut toml::map::Map<String, Value> = entry.as_table_mut()?;
let path_matches: bool = entry_table
.get("path")
.and_then(|path_value: &Value| path_value.as_str())
.map(|s: &str| s == member_path)
.unwrap_or(false);
if !path_matches {
return None;
}
entry_table.insert(
"version".to_string(),
Value::String(workspace_version.to_string()),
);
if current_alias != canonical_alias {
let renamed: (String, String) = (current_alias.to_string(), canonical_alias.to_string());
let new_entry: Value = entry.clone();
deps.remove(current_alias);
let entry_to_set: &mut Value = deps.entry(canonical_alias.to_string()).or_insert(new_entry);
let canonical_table: &mut toml::map::Map<String, Value> =
entry_to_set.as_table_mut().unwrap();
canonical_table.insert("path".to_string(), Value::String(member_path.to_string()));
canonical_table.insert(
"version".to_string(),
Value::String(workspace_version.to_string()),
);
Some(renamed)
} else {
None
}
}
pub async fn execute_sync(manifest_path: &str) -> Result<SyncReport, SyncError> {
let path: &Path = Path::new(manifest_path);
let content: String = read_to_string(path).await?;
let mut doc: Value = toml::from_str(&content).map_err(|_| SyncError::ManifestParseError)?;
let workspace_version: String = read_workspace_version(&doc)?;
let members: Vec<String> = read_workspace_members(&doc)?;
if members.is_empty() {
log::info!("sync: no workspace members, nothing to do");
return Ok(SyncReport {
workspace_version,
renamed_entries: Vec::new(),
versioned_entries: Vec::new(),
file_changed: false,
});
}
let mut renamed_entries: Vec<(String, String)> = Vec::new();
let mut versioned_entries: Vec<(String, String)> = Vec::new();
let mut needs_rewrite: bool = false;
let deps_value: Option<&Value> = doc
.get("workspace")
.and_then(|workspace: &Value| workspace.get("dependencies"));
let mut deps: toml::map::Map<String, Value> = deps_value
.and_then(|value: &Value| value.as_table())
.cloned()
.unwrap_or_default();
for member_path in &members {
let member_manifest_path: PathBuf = path
.parent()
.unwrap_or_else(|| Path::new("."))
.join(member_path)
.join("Cargo.toml");
if !member_manifest_path.exists() {
return Err(SyncError::MemberManifestMissing(
member_manifest_path.display().to_string(),
));
}
let member_content: String = read_to_string(&member_manifest_path).await?;
let member_doc: Value =
toml::from_str(&member_content).map_err(|_| SyncError::ManifestParseError)?;
let canonical_alias: String = read_member_crate_name(&member_doc)?;
let current_alias: Option<String> =
find_dep_alias_for_member_path(&Value::Table(deps.clone()), member_path);
let current_alias: String = match current_alias {
Some(alias) => alias,
None => {
log::info!(
"sync: {} -> no [workspace.dependencies] entry, skipping",
member_path
);
continue;
}
};
let existing_version: Option<String> = deps
.get(¤t_alias)
.and_then(|entry: &Value| entry.as_table())
.and_then(|table: &toml::map::Map<String, Value>| table.get("version"))
.and_then(|version_value: &Value| version_value.as_str())
.map(|s: &str| s.to_string());
let alias_needs_rename: bool = current_alias != canonical_alias;
let version_needs_rewrite: bool = existing_version
.as_deref()
.map(|existing: &str| existing != workspace_version)
.unwrap_or(true);
if !alias_needs_rename && !version_needs_rewrite {
continue;
}
needs_rewrite = true;
let renamed: Option<(String, String)> = rewrite_dep_entry(
&mut deps,
member_path,
¤t_alias,
&canonical_alias,
&workspace_version,
);
if let Some((old, new)) = &renamed {
log::info!("sync: {} renamed {} -> {}", member_path, old, new);
renamed_entries.push((old.clone(), new.clone()));
} else {
log::info!(
"sync: {} -> {} v{}",
member_path,
canonical_alias,
workspace_version
);
}
versioned_entries.push((member_path.clone(), canonical_alias));
}
let file_changed: bool = needs_rewrite;
if file_changed {
if let Some(workspace_table) = doc
.get_mut("workspace")
.and_then(|workspace: &mut Value| workspace.as_table_mut())
{
workspace_table.insert("dependencies".to_string(), Value::Table(deps.clone()));
}
let updated_content: String =
toml::to_string(&doc).map_err(|_| SyncError::ManifestSerializeError)?;
write(path, updated_content).await?;
log::info!(
"sync: wrote {} entries to v{}",
versioned_entries.len(),
workspace_version
);
} else {
log::info!("sync: already in sync: v{}", workspace_version);
}
Ok(SyncReport {
workspace_version,
renamed_entries,
versioned_entries,
file_changed,
})
}