use crate::workspace::WorkspacePath;
use anyhow::{anyhow, Result};
pub(super) fn join_workspace_path(base: &WorkspacePath, rel: &str) -> WorkspacePath {
if base.is_root() {
WorkspacePath::from_normalized(rel)
} else {
WorkspacePath::from_normalized(format!("{}/{}", base.as_str(), rel))
}
}
pub(super) fn basename(rel: &str) -> &str {
rel.rsplit_once('/').map_or(rel, |(_, b)| b)
}
pub(super) fn validate_content_length(
advertised: Option<i64>,
max_bytes: u64,
bucket: &str,
key: &str,
) -> Result<()> {
match advertised {
Some(n) if n < 0 => Err(anyhow!(
"S3 object s3://{}/{} reported invalid content-length {}",
bucket,
key,
n
)),
Some(n) if (n as u64) > max_bytes => Err(anyhow!(
"S3 object s3://{}/{} is {} bytes, exceeds workspace max_read_bytes ({}); \
raise S3BackendConfig::max_read_bytes if the read is legitimate",
bucket,
key,
n,
max_bytes
)),
Some(_) => Ok(()),
None => Err(anyhow!(
"S3 object s3://{}/{} did not report Content-Length; refusing to read \
without a size guard. Check that the endpoint is S3-compliant.",
bucket,
key
)),
}
}
pub(super) fn normalize_prefix(prefix: &str) -> String {
prefix
.trim_start_matches('/')
.trim_end_matches('/')
.to_string()
}
pub(super) fn strip_dir_name(common_prefix: &str, listing_prefix: &str) -> Option<String> {
let remainder = common_prefix.strip_prefix(listing_prefix)?;
let trimmed = remainder.trim_end_matches('/');
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
pub(super) fn strip_file_name(key: &str, listing_prefix: &str) -> Option<String> {
let remainder = key.strip_prefix(listing_prefix)?;
if remainder.is_empty() || remainder.contains('/') {
None
} else {
Some(remainder.to_string())
}
}