const MAX_NAME_LEN: usize = 64;
const HASH_HEX_LEN: usize = 6;
const SUFFIX_LEN: usize = 1 + HASH_HEX_LEN;
pub fn sanitize(server: &str, tool: &str) -> String {
let original = format!("{server}__{tool}");
let mut sanitized = String::with_capacity(original.len());
for c in original.chars() {
if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
sanitized.push(c);
} else {
sanitized.push('_');
}
}
if sanitized.len() <= MAX_NAME_LEN {
return sanitized;
}
let hash = blake3::hash(original.as_bytes());
let hex = hash.to_hex();
let suffix_hex = &hex.as_str()[..HASH_HEX_LEN];
sanitized.truncate(MAX_NAME_LEN - SUFFIX_LEN);
sanitized.push('_');
sanitized.push_str(suffix_hex);
sanitized
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_basic() {
assert_eq!(sanitize("fs", "read_file"), "fs__read_file");
}
#[test]
fn sanitize_replaces_invalid_chars() {
let got = sanitize("fs", "weird/name with spaces!");
assert_eq!(got, "fs__weird_name_with_spaces_");
assert!(got.starts_with("fs__"));
for c in got.chars() {
assert!(
c.is_ascii_alphanumeric() || c == '_' || c == '-',
"char {c:?} survived sanitization"
);
}
}
#[test]
fn sanitize_short_names_unchanged_after_replacement() {
let got = sanitize("server-01", "do_a_thing");
assert_eq!(got, "server-01__do_a_thing");
}
#[test]
fn sanitize_truncates_with_stable_hash_suffix() {
let server = "fs";
let tool = "x".repeat(120);
let got = sanitize(server, &tool);
assert_eq!(got.len(), 64, "got: {got:?}");
let again = sanitize(server, &tool);
assert_eq!(got, again, "sanitize must be deterministic");
}
#[test]
fn sanitize_distinguishes_long_inputs_with_shared_prefix() {
let prefix = "p".repeat(100);
let a = sanitize("srv", &format!("{prefix}_aaaa"));
let b = sanitize("srv", &format!("{prefix}_bbbb"));
assert_ne!(
a, b,
"distinct originals must yield distinct sanitized names"
);
assert_eq!(a.len(), 64);
assert_eq!(b.len(), 64);
let a_prefix = &a[..a.len() - 7];
let b_prefix = &b[..b.len() - 7];
assert_eq!(
a_prefix, b_prefix,
"shared 100-char prefix should survive truncation identically"
);
assert_ne!(
&a[a.len() - 6..],
&b[b.len() - 6..],
"hash suffixes must differ"
);
}
#[test]
fn sanitize_truncated_output_still_charset_clean() {
let tool = format!("{}{}{}", "a".repeat(50), "/", "b".repeat(50));
let got = sanitize("svr", &tool);
assert_eq!(got.len(), 64);
for c in got.chars() {
assert!(
c.is_ascii_alphanumeric() || c == '_' || c == '-',
"char {c:?} survived sanitization in truncated form"
);
}
}
}