1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
7use std::path::Path;
8
9pub mod prelude;
10
11const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
12const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
13
14#[must_use]
15pub fn stable_hash64(bytes: &[u8]) -> u64 {
16 bytes.iter().fold(FNV_OFFSET_BASIS, |hash, byte| {
17 (hash ^ u64::from(*byte)).wrapping_mul(FNV_PRIME)
18 })
19}
20
21#[must_use]
22pub fn stable_hex(bytes: &[u8]) -> String {
23 format!("{:016x}", stable_hash64(bytes))
24}
25
26#[must_use]
27pub fn stable_name_id(name: &str) -> StableId {
28 StableId::from_bytes(name.as_bytes())
29}
30
31#[must_use]
32pub fn stable_content_id(content: &[u8]) -> StableId {
33 StableId::from_bytes(content)
34}
35
36#[must_use]
37pub fn stable_reference(namespace: &str, name: &str) -> StableId {
38 StableId::from_bytes(format!("{namespace}:{name}").as_bytes())
39}
40
41#[must_use]
42pub fn stable_path_id(path: impl AsRef<Path>) -> StableId {
43 StableId::from_path(path)
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
47pub struct StableId(String);
48
49impl StableId {
50 #[must_use]
51 pub fn from_bytes(bytes: &[u8]) -> Self {
52 Self(stable_hex(bytes))
53 }
54
55 #[must_use]
56 pub fn from_name(name: &str) -> Self {
57 stable_name_id(name)
58 }
59
60 #[must_use]
61 pub fn from_path(path: impl AsRef<Path>) -> Self {
62 let normalized = path.as_ref().to_string_lossy().replace('\\', "/");
63 Self::from_bytes(normalized.as_bytes())
64 }
65
66 #[must_use]
67 pub fn as_str(&self) -> &str {
68 &self.0
69 }
70
71 #[must_use]
72 pub fn into_inner(self) -> String {
73 self.0
74 }
75}
76
77impl fmt::Display for StableId {
78 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79 formatter.write_str(self.as_str())
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::{StableId, stable_name_id, stable_path_id, stable_reference};
86
87 #[test]
88 fn name_ids_are_deterministic() {
89 assert_eq!(stable_name_id("rustuse"), stable_name_id("rustuse"));
90 }
91
92 #[test]
93 fn path_ids_normalize_separators() {
94 assert_eq!(
95 stable_path_id("docs\\intro.md"),
96 stable_path_id("docs/intro.md")
97 );
98 }
99
100 #[test]
101 fn namespace_changes_the_output() {
102 assert_ne!(
103 stable_reference("docs", "intro"),
104 StableId::from_name("intro")
105 );
106 }
107}