1use sha2::{Digest, Sha256};
2
3pub fn sanitize_fragment(value: &str) -> String {
4 let mut out = String::with_capacity(value.len());
5 for ch in value.chars() {
6 if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
7 out.push(ch);
8 } else {
9 out.push('_');
10 }
11 }
12 while out.contains("__") {
13 out = out.replace("__", "_");
14 }
15 out.trim_matches('_').to_owned()
16}
17
18pub fn short_sha256(value: &str) -> String {
19 let mut hasher = Sha256::new();
20 hasher.update(value.as_bytes());
21 hex::encode(hasher.finalize())[..12].to_owned()
22}
23
24pub fn document_id(
25 repo: &str,
26 kind: &str,
27 source_path: Option<&str>,
28 line: Option<u32>,
29 name: Option<&str>,
30) -> String {
31 let raw = format!(
32 "{repo}|{kind}|{}|{}|{}",
33 source_path.unwrap_or(""),
34 line.map(|item| item.to_string()).unwrap_or_default(),
35 name.unwrap_or("")
36 );
37 format!(
38 "{}__{}__{}",
39 sanitize_fragment(repo),
40 sanitize_fragment(kind),
41 short_sha256(&raw)
42 )
43}
44
45pub fn is_safe_document_id(value: &str) -> bool {
46 !value.is_empty()
47 && value
48 .chars()
49 .all(|ch| ch.is_ascii_alphanumeric() || ch == '-' || ch == '_')
50}