Skip to main content

haystack_core/ontology/
def.rs

1// Haystack 4 def record -- a single definition in the ontology.
2
3use crate::data::HDict;
4
5/// Classification of a Haystack 4 def.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum DefKind {
8    /// Marker tag (default).
9    Marker,
10    /// Value type (val or scalar).
11    Val,
12    /// Entity type.
13    Entity,
14    /// Feature namespace.
15    Feature,
16    /// Compound tag (has "-" in symbol, e.g. "hot-water").
17    Conjunct,
18    /// Choice option.
19    Choice,
20    /// Library def.
21    Lib,
22}
23
24/// A single Haystack 4 definition loaded from Trio.
25///
26/// Each def has a symbol (name), belongs to a library, and has an
27/// inheritance chain via the `is` tag. Additional metadata such as
28/// `tagOn`, `of`, `mandatory`, and `doc` describe how the def relates
29/// to entity types and other defs.
30#[derive(Debug, Clone)]
31pub struct Def {
32    /// Def name, e.g. `"ahu"` or `"lib:phIoT"`.
33    pub symbol: String,
34    /// Library name, e.g. `"phIoT"`.
35    pub lib: String,
36    /// Supertype symbols from the `is` tag.
37    pub is_: Vec<String>,
38    /// Entity types this tag applies to (`tagOn`).
39    pub tag_on: Vec<String>,
40    /// Target type for refs/choices (`of` tag).
41    pub of: Option<String>,
42    /// Whether this tag is mandatory on entities.
43    pub mandatory: bool,
44    /// Human-readable documentation string.
45    pub doc: String,
46    /// Full HDict of all meta tags on this def.
47    pub tags: HDict,
48}
49
50impl Def {
51    /// Derive the def kind from its supertype chain.
52    ///
53    /// Priority order:
54    /// 1. `"lib"` in is_ -> `DefKind::Lib`
55    /// 2. `"choice"` in is_ -> `DefKind::Choice`
56    /// 3. `"entity"` in is_ -> `DefKind::Entity`
57    /// 4. `"val"` or `"scalar"` in is_ -> `DefKind::Val`
58    /// 5. `"feature"` in is_ -> `DefKind::Feature`
59    /// 6. `"-"` in symbol -> `DefKind::Conjunct`
60    /// 7. default -> `DefKind::Marker`
61    pub fn kind(&self) -> DefKind {
62        if self.is_.iter().any(|s| s == "lib") {
63            return DefKind::Lib;
64        }
65        if self.is_.iter().any(|s| s == "choice") {
66            return DefKind::Choice;
67        }
68        if self.is_.iter().any(|s| s == "entity") {
69            return DefKind::Entity;
70        }
71        if self.is_.iter().any(|s| s == "val" || s == "scalar") {
72            return DefKind::Val;
73        }
74        if self.is_.iter().any(|s| s == "feature") {
75            return DefKind::Feature;
76        }
77        if self.symbol.contains('-') {
78            return DefKind::Conjunct;
79        }
80        DefKind::Marker
81    }
82}
83
84impl PartialEq for Def {
85    fn eq(&self, other: &Self) -> bool {
86        self.symbol == other.symbol && self.lib == other.lib
87    }
88}
89
90impl Eq for Def {}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    fn make_def(symbol: &str, is_: &[&str]) -> Def {
97        Def {
98            symbol: symbol.to_string(),
99            lib: "test".to_string(),
100            is_: is_.iter().map(|s| s.to_string()).collect(),
101            tag_on: vec![],
102            of: None,
103            mandatory: false,
104            doc: String::new(),
105            tags: HDict::new(),
106        }
107    }
108
109    #[test]
110    fn kind_marker_default() {
111        let d = make_def("site", &["marker"]);
112        assert_eq!(d.kind(), DefKind::Marker);
113    }
114
115    #[test]
116    fn kind_lib() {
117        let d = make_def("lib:phIoT", &["lib"]);
118        assert_eq!(d.kind(), DefKind::Lib);
119    }
120
121    #[test]
122    fn kind_choice() {
123        let d = make_def("ahuZoneDelivery", &["choice"]);
124        assert_eq!(d.kind(), DefKind::Choice);
125    }
126
127    #[test]
128    fn kind_entity() {
129        let d = make_def("site", &["entity"]);
130        assert_eq!(d.kind(), DefKind::Entity);
131    }
132
133    #[test]
134    fn kind_val() {
135        let d = make_def("number", &["val"]);
136        assert_eq!(d.kind(), DefKind::Val);
137    }
138
139    #[test]
140    fn kind_val_via_scalar() {
141        let d = make_def("bool", &["scalar"]);
142        assert_eq!(d.kind(), DefKind::Val);
143    }
144
145    #[test]
146    fn kind_feature() {
147        let d = make_def("filetype", &["feature"]);
148        assert_eq!(d.kind(), DefKind::Feature);
149    }
150
151    #[test]
152    fn kind_conjunct() {
153        let d = make_def("hot-water", &["marker"]);
154        // Even though is_ has "marker", the "-" in symbol wins for conjunct
155        // Actually, conjunct only triggers if none of the higher-priority
156        // checks match. "marker" doesn't match any priority check, so
157        // the "-" in symbol triggers conjunct.
158        assert_eq!(d.kind(), DefKind::Conjunct);
159    }
160
161    #[test]
162    fn kind_lib_takes_priority_over_conjunct() {
163        // Lib check has higher priority than conjunct
164        let d = make_def("lib:ph-test", &["lib"]);
165        assert_eq!(d.kind(), DefKind::Lib);
166    }
167
168    #[test]
169    fn def_equality() {
170        let a = make_def("site", &["marker"]);
171        let b = make_def("site", &["marker"]);
172        assert_eq!(a, b);
173    }
174
175    #[test]
176    fn def_inequality() {
177        let a = make_def("site", &["marker"]);
178        let b = make_def("equip", &["marker"]);
179        assert_ne!(a, b);
180    }
181}