Skip to main content

haystack_core/kinds/
ref_.rs

1// Haystack Ref — an entity reference.
2
3use std::fmt;
4use std::hash::{Hash, Hasher};
5
6/// Haystack Ref — an entity reference.
7///
8/// `val` is the identifier (alphanumeric, `_`, `:`, `-`, `.`, `~`).
9/// `dis` is an optional display name (cosmetic — ignored in equality/hash).
10///
11/// Zinc: `@abc-123` or `@abc-123 "Display Name"`.
12#[derive(Debug, Clone)]
13pub struct HRef {
14    pub val: String,
15    pub dis: Option<String>,
16}
17
18impl HRef {
19    pub fn new(val: impl Into<String>, dis: Option<String>) -> Self {
20        Self {
21            val: val.into(),
22            dis,
23        }
24    }
25
26    pub fn from_val(val: impl Into<String>) -> Self {
27        Self {
28            val: val.into(),
29            dis: None,
30        }
31    }
32}
33
34impl PartialEq for HRef {
35    fn eq(&self, other: &Self) -> bool {
36        self.val == other.val
37    }
38}
39
40impl Eq for HRef {}
41
42impl Hash for HRef {
43    fn hash<H: Hasher>(&self, state: &mut H) {
44        self.val.hash(state);
45    }
46}
47
48impl fmt::Display for HRef {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(f, "@{}", self.val)?;
51        if let Some(ref dis) = self.dis {
52            write!(f, " '{dis}'")?;
53        }
54        Ok(())
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use std::collections::HashSet;
62
63    #[test]
64    fn ref_display_without_dis() {
65        let r = HRef::from_val("site-1");
66        assert_eq!(r.to_string(), "@site-1");
67    }
68
69    #[test]
70    fn ref_display_with_dis() {
71        let r = HRef::new("site-1", Some("Main Site".into()));
72        assert_eq!(r.to_string(), "@site-1 'Main Site'");
73    }
74
75    #[test]
76    fn ref_equality_ignores_dis() {
77        let a = HRef::new("site-1", Some("Building A".into()));
78        let b = HRef::new("site-1", Some("Different Name".into()));
79        let c = HRef::from_val("site-1");
80        assert_eq!(a, b);
81        assert_eq!(a, c);
82    }
83
84    #[test]
85    fn ref_hash_ignores_dis() {
86        let a = HRef::new("site-1", Some("A".into()));
87        let b = HRef::new("site-1", Some("B".into()));
88        let mut set = HashSet::new();
89        set.insert(a);
90        assert!(set.contains(&b));
91    }
92
93    #[test]
94    fn ref_inequality() {
95        let a = HRef::from_val("site-1");
96        let b = HRef::from_val("site-2");
97        assert_ne!(a, b);
98    }
99
100    #[test]
101    fn ref_from_val_convenience() {
102        let r = HRef::from_val("abc");
103        assert_eq!(r.val, "abc");
104        assert_eq!(r.dis, None);
105    }
106}