Skip to main content

capsula_core/
util.rs

1//! Small pure-helper utilities shared across Capsula crates.
2
3use std::path::{Path, PathBuf};
4
5/// Encode bytes as a lowercase hexadecimal string.
6#[must_use]
7pub fn hex_encode(bytes: &[u8]) -> String {
8    bytes
9        .iter()
10        .fold(String::with_capacity(bytes.len() * 2), |mut output, b| {
11            use std::fmt::Write;
12            let _ = write!(output, "{b:02x}");
13            output
14        })
15}
16
17/// Resolve a path that may be absolute or relative to `project_root`.
18///
19/// For absolute paths, returns a clone. For relative paths, joins against
20/// `project_root` and canonicalizes (which requires the path to exist).
21pub fn resolve_relative(path: &Path, project_root: &Path) -> std::io::Result<PathBuf> {
22    if path.is_absolute() {
23        Ok(path.to_path_buf())
24    } else {
25        project_root.join(path).canonicalize()
26    }
27}
28
29#[cfg(test)]
30#[expect(clippy::unwrap_used, reason = "tests may use unwrap for brevity")]
31mod tests {
32    use super::{hex_encode, resolve_relative};
33    use std::path::Path;
34
35    #[test]
36    fn hex_encode_empty() {
37        assert_eq!(hex_encode(&[]), "");
38    }
39
40    #[test]
41    fn hex_encode_single_bytes() {
42        assert_eq!(hex_encode(&[0x00]), "00");
43        assert_eq!(hex_encode(&[0xff]), "ff");
44        assert_eq!(hex_encode(&[0xab, 0xcd, 0xef]), "abcdef");
45    }
46
47    #[test]
48    fn resolve_relative_passes_absolute_through() {
49        let abs = Path::new("/does/not/exist/absolute");
50        let resolved = resolve_relative(abs, Path::new("/tmp")).unwrap();
51        assert_eq!(resolved, abs);
52    }
53
54    #[test]
55    fn resolve_relative_joins_relative_and_canonicalizes() {
56        // project_root = cwd so canonicalize can resolve it.
57        let cwd = std::env::current_dir().unwrap();
58        let resolved = resolve_relative(Path::new("."), &cwd).unwrap();
59        // canonicalize resolves symlinks; on macOS this is /private/var/... etc.
60        assert_eq!(resolved, cwd.canonicalize().unwrap());
61    }
62}