Skip to main content

sdivi_core/input/
edge_weight.rs

1//! Helper for constructing [`LeidenConfigInput::edge_weights`] keys.
2
3/// Separator used in [`edge_weight_key`] to encode an edge pair as a single string.
4///
5/// NUL (`\x00`) never appears in canonical node IDs (which are repo-relative
6/// file paths) so it is safe as a delimiter.
7const SEP: char = '\x00';
8
9/// Encodes a `(source, target)` node-ID pair as an edge-weight map key.
10///
11/// The key format required by [`super::LeidenConfigInput::edge_weights`] is
12/// `"source\x00target"` (NUL-separated). Callers should canonicalize
13/// (`source < target` by lexicographic order); mis-ordered pairs are
14/// normalized by the detection layer, so no weight is silently discarded.
15///
16/// # Examples
17///
18/// ```rust
19/// use sdivi_core::input::edge_weight_key;
20///
21/// let key = edge_weight_key("src/a.rs", "src/b.rs");
22/// assert_eq!(key, "src/a.rs\x00src/b.rs");
23/// ```
24pub fn edge_weight_key(source: &str, target: &str) -> String {
25    format!("{source}{SEP}{target}")
26}
27
28/// Splits an edge-weight map key produced by [`edge_weight_key`] into `(source, target)`.
29///
30/// Returns `None` if the key does not contain the separator.
31///
32/// # Examples
33///
34/// ```rust
35/// use sdivi_core::input::{edge_weight_key, split_edge_weight_key};
36///
37/// let key = edge_weight_key("src/a.rs", "src/b.rs");
38/// let (s, t) = split_edge_weight_key(&key).unwrap();
39/// assert_eq!(s, "src/a.rs");
40/// assert_eq!(t, "src/b.rs");
41/// ```
42pub fn split_edge_weight_key(key: &str) -> Option<(&str, &str)> {
43    key.split_once(SEP)
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn round_trip() {
52        let k = edge_weight_key("foo/a.rs", "foo/b.rs");
53        let (s, t) = split_edge_weight_key(&k).unwrap();
54        assert_eq!(s, "foo/a.rs");
55        assert_eq!(t, "foo/b.rs");
56    }
57
58    #[test]
59    fn split_missing_sep_returns_none() {
60        assert!(split_edge_weight_key("no-separator-here").is_none());
61    }
62
63    #[test]
64    fn edge_weight_key_with_repo_paths() {
65        let k = edge_weight_key("src/components/ui.rs", "src/utils/helpers.rs");
66        assert_eq!(k, "src/components/ui.rs\x00src/utils/helpers.rs");
67
68        let (s, t) = split_edge_weight_key(&k).unwrap();
69        assert_eq!(s, "src/components/ui.rs");
70        assert_eq!(t, "src/utils/helpers.rs");
71    }
72
73    #[test]
74    fn edge_weight_key_with_special_path_characters() {
75        let k = edge_weight_key("src/file-name_1.rs", "tests/util.rs");
76        assert_eq!(k, "src/file-name_1.rs\x00tests/util.rs");
77
78        let (s, t) = split_edge_weight_key(&k).unwrap();
79        assert_eq!(s, "src/file-name_1.rs");
80        assert_eq!(t, "tests/util.rs");
81    }
82
83    #[test]
84    fn edge_weight_key_with_nested_paths() {
85        let k = edge_weight_key("a/b/c/d.rs", "x/y/z/w.rs");
86        assert_eq!(k, "a/b/c/d.rs\x00x/y/z/w.rs");
87
88        let (s, t) = split_edge_weight_key(&k).unwrap();
89        assert_eq!(s, "a/b/c/d.rs");
90        assert_eq!(t, "x/y/z/w.rs");
91    }
92
93    #[test]
94    fn edge_weight_key_multiple_separators_in_path_splits_on_first() {
95        // If a path somehow contained NUL (which shouldn't happen in practice),
96        // split should split on the first occurrence.
97        let malformed = format!("a{}b{}c", SEP, SEP);
98        let (s, t) = split_edge_weight_key(&malformed).unwrap();
99        assert_eq!(s, "a");
100        assert_eq!(t, format!("b{}c", SEP));
101    }
102}