use super::outputs::CacheCrateOutput;
use anyhow::{Context, Result, bail};
use std::fs;
use std::path::Path;
pub fn copy_directory_contents(src: &Path, dest: &Path) -> Result<()> {
if !src.exists() {
bail!("Source directory does not exist: {}", src.display());
}
if !dest.exists() {
fs::create_dir_all(dest)
.with_context(|| format!("Failed to create directory: {}", dest.display()))?;
}
for entry in
fs::read_dir(src).with_context(|| format!("Failed to read directory: {}", src.display()))?
{
let entry = entry?;
let path = entry.path();
let name = entry.file_name();
let dest_path = dest.join(&name);
if path.is_dir() {
if name == ".git" || name == ".svn" || name == ".hg" || name == "target" {
continue;
}
copy_directory_contents(&path, &dest_path)?;
} else {
fs::copy(&path, &dest_path).with_context(|| {
format!(
"Failed to copy file from {} to {}",
path.display(),
dest_path.display()
)
})?;
}
}
Ok(())
}
pub fn format_bytes(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
if bytes == 0 {
return "0 B".to_string();
}
let base = 1024_f64;
let exponent = (bytes as f64).ln() / base.ln();
let exponent = exponent.floor() as usize;
let unit = UNITS.get(exponent).unwrap_or(&"TB");
let size = bytes as f64 / base.powi(exponent as i32);
if size.fract() == 0.0 {
format!("{size:.0} {unit}")
} else {
format!("{size:.2} {unit}")
}
}
pub type CacheResponse = CacheCrateOutput;
impl CacheResponse {
pub fn success(crate_name: impl Into<String>, version: impl Into<String>) -> Self {
let crate_name = crate_name.into();
let version = version.into();
Self::Success {
message: format!("Successfully cached {crate_name}-{version}"),
crate_name,
version,
members: None,
results: None,
updated: None,
}
}
pub fn success_updated(crate_name: impl Into<String>, version: impl Into<String>) -> Self {
let crate_name = crate_name.into();
let version = version.into();
Self::Success {
message: format!("Successfully updated {crate_name}-{version}"),
crate_name,
version,
members: None,
results: None,
updated: Some(true),
}
}
pub fn members_success(
crate_name: impl Into<String>,
version: impl Into<String>,
members: Vec<String>,
results: Vec<String>,
updated: bool,
) -> Self {
let count = results.len();
let message = if updated {
format!("Successfully updated {count} workspace members")
} else {
format!("Successfully cached {count} workspace members")
};
Self::Success {
message,
crate_name: crate_name.into(),
version: version.into(),
members: Some(members),
results: Some(results),
updated: if updated { Some(true) } else { None },
}
}
pub fn members_partial(
crate_name: impl Into<String>,
version: impl Into<String>,
members: Vec<String>,
results: Vec<String>,
errors: Vec<String>,
updated: bool,
) -> Self {
let message = if updated {
format!(
"Updated {} members with {} errors",
results.len(),
errors.len()
)
} else {
format!(
"Cached {} members with {} errors",
results.len(),
errors.len()
)
};
Self::PartialSuccess {
message,
crate_name: crate_name.into(),
version: version.into(),
members,
results,
errors,
updated: if updated { Some(true) } else { None },
}
}
pub fn workspace_detected(
crate_name: impl Into<String>,
version: impl Into<String>,
members: Vec<String>,
source_type: &str,
updated: bool,
) -> Self {
let crate_name = crate_name.into();
let version = version.into();
let example_members = members.get(0..2.min(members.len())).unwrap_or(&[]).to_vec();
Self::WorkspaceDetected {
message: "This is a workspace crate. Please specify which members to cache using the 'members' parameter.".to_string(),
crate_name: crate_name.clone(),
version: version.clone(),
workspace_members: members,
example_usage: format!(
"cache_crate_from_{source_type}(crate_name=\"{crate_name}\", version=\"{version}\", members={example_members:?})"
),
updated: if updated { Some(true) } else { None },
}
}
pub fn error(message: impl Into<String>) -> Self {
Self::Error {
error: message.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_format_bytes() {
assert_eq!(format_bytes(0), "0 B");
assert_eq!(format_bytes(512), "512 B");
assert_eq!(format_bytes(1024), "1 KB");
assert_eq!(format_bytes(1536), "1.50 KB");
assert_eq!(format_bytes(1048576), "1 MB");
assert_eq!(format_bytes(1073741824), "1 GB");
}
#[test]
fn test_copy_directory_contents() -> Result<()> {
let temp_dir = TempDir::new()?;
let src_dir = temp_dir.path().join("src");
let dest_dir = temp_dir.path().join("dest");
fs::create_dir_all(&src_dir)?;
fs::write(src_dir.join("file1.txt"), "content1")?;
let sub_dir = src_dir.join("subdir");
fs::create_dir_all(&sub_dir)?;
fs::write(sub_dir.join("file2.txt"), "content2")?;
let git_dir = src_dir.join(".git");
fs::create_dir_all(&git_dir)?;
fs::write(git_dir.join("config"), "git config")?;
copy_directory_contents(&src_dir, &dest_dir)?;
assert!(dest_dir.join("file1.txt").exists());
assert!(dest_dir.join("subdir").exists());
assert!(dest_dir.join("subdir/file2.txt").exists());
assert!(!dest_dir.join(".git").exists());
Ok(())
}
#[test]
fn test_cache_response() {
let response = CacheResponse::success("test-crate", "1.0.0");
let json_str = response.to_json();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(json["status"], "success");
assert_eq!(json["message"], "Successfully cached test-crate-1.0.0");
assert_eq!(json["crate"], "test-crate");
assert_eq!(json["version"], "1.0.0");
let error = CacheResponse::error("Something went wrong");
let json_str = error.to_json();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(json["status"], "error");
assert_eq!(json["error"], "Something went wrong");
let workspace = CacheResponse::workspace_detected(
"test-crate",
"1.0.0",
vec!["crate-a".to_string(), "crate-b".to_string()],
"cratesio",
false,
);
let json_str = workspace.to_json();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(json["status"], "workspace_detected");
assert_eq!(json["crate"], "test-crate");
assert_eq!(
json["workspace_members"],
serde_json::json!(["crate-a", "crate-b"])
);
let members = CacheResponse::members_success(
"test-crate",
"1.0.0",
vec!["member1".to_string()],
vec!["Successfully cached member: member1".to_string()],
false,
);
let json_str = members.to_json();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(json["status"], "success");
assert!(json.get("updated").is_none());
let members_updated = CacheResponse::members_success(
"test-crate",
"1.0.0",
vec!["member1".to_string()],
vec!["Successfully cached member: member1".to_string()],
true,
);
let json_str = members_updated.to_json();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert!(json["updated"].as_bool().unwrap());
}
}