Skip to main content

telltale_language/ast/
annotation_collection.rs

1use super::ProtocolAnnotation;
2use std::collections::HashMap;
3use std::time::Duration;
4
5/// A collection of protocol annotations with typed accessors.
6#[derive(Debug, Clone, Default, PartialEq)]
7pub struct Annotations {
8    items: Vec<ProtocolAnnotation>,
9}
10
11impl Annotations {
12    /// Create an empty annotation set.
13    #[must_use]
14    pub fn new() -> Self {
15        Self { items: Vec::new() }
16    }
17
18    /// Create from a single annotation.
19    #[must_use]
20    pub fn single(annotation: ProtocolAnnotation) -> Self {
21        Self {
22            items: vec![annotation],
23        }
24    }
25
26    /// Create from a vector of annotations.
27    #[must_use]
28    pub fn from_vec(items: Vec<ProtocolAnnotation>) -> Self {
29        Self { items }
30    }
31
32    pub fn from_dsl_map(map: &HashMap<String, String>) -> Self {
33        let mut items = Vec::new();
34
35        if map.get("timed_choice").is_some_and(|v| v == "true") {
36            let duration = map
37                .get("timeout_ms")
38                .and_then(|v| v.parse::<u64>().ok())
39                .map(Duration::from_millis)
40                .unwrap_or_else(|| Duration::from_secs(5));
41            items.push(ProtocolAnnotation::TimedChoice { duration });
42        }
43
44        let mut entries: Vec<_> = map.iter().collect();
45        entries.sort_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b));
46        for (key, value) in entries {
47            if key == "timed_choice" || key == "timeout_ms" {
48                continue;
49            }
50            items.push(ProtocolAnnotation::parse_dsl_entry(key, value));
51        }
52
53        Self { items }
54    }
55
56    pub fn dsl_map(&self) -> HashMap<String, String> {
57        let mut map = HashMap::new();
58
59        for annotation in &self.items {
60            for (key, value) in annotation.dsl_entries() {
61                map.insert(key, value);
62            }
63        }
64
65        map
66    }
67
68    /// Add an annotation.
69    pub fn push(&mut self, annotation: ProtocolAnnotation) {
70        self.items.push(annotation);
71    }
72
73    /// Check if empty.
74    #[must_use]
75    pub fn is_empty(&self) -> bool {
76        self.items.is_empty()
77    }
78
79    /// Get the number of annotations.
80    #[must_use]
81    pub fn len(&self) -> usize {
82        self.items.len()
83    }
84
85    /// Iterate over annotations.
86    pub fn iter(&self) -> impl Iterator<Item = &ProtocolAnnotation> {
87        self.items.iter()
88    }
89
90    /// Iterate mutably over annotations.
91    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ProtocolAnnotation> {
92        self.items.iter_mut()
93    }
94
95    /// Check if any annotation satisfies a predicate.
96    #[must_use]
97    pub fn any<F>(&self, f: F) -> bool
98    where
99        F: FnMut(&ProtocolAnnotation) -> bool,
100    {
101        self.items.iter().any(f)
102    }
103
104    /// Find the first annotation satisfying a predicate.
105    #[must_use]
106    pub fn find<F>(&self, f: F) -> Option<&ProtocolAnnotation>
107    where
108        F: FnMut(&&ProtocolAnnotation) -> bool,
109    {
110        self.items.iter().find(f)
111    }
112
113    /// Get timed choice annotation if present.
114    #[must_use]
115    pub fn timed_choice(&self) -> Option<Duration> {
116        self.items.iter().find_map(|a| a.timed_choice_duration())
117    }
118
119    /// Check if this has a timed choice annotation.
120    #[must_use]
121    pub fn has_timed_choice(&self) -> bool {
122        self.items.iter().any(|a| a.is_timed_choice())
123    }
124
125    /// Get priority annotation if present.
126    #[must_use]
127    pub fn priority(&self) -> Option<u32> {
128        self.items.iter().find_map(|a| a.priority_value())
129    }
130
131    /// Check if marked as idempotent.
132    #[must_use]
133    pub fn is_idempotent(&self) -> bool {
134        self.items.iter().any(|a| a.is_idempotent())
135    }
136
137    /// Check if has any trace annotation.
138    #[must_use]
139    pub fn has_trace(&self) -> bool {
140        self.items.iter().any(|a| a.is_trace())
141    }
142
143    /// Check if has a heartbeat annotation.
144    #[must_use]
145    pub fn has_heartbeat(&self) -> bool {
146        self.items.iter().any(|a| a.is_heartbeat())
147    }
148
149    /// Get heartbeat parameters if present (interval, on_missing_count).
150    #[must_use]
151    pub fn heartbeat(&self) -> Option<(Duration, u32)> {
152        self.items.iter().find_map(|a| a.heartbeat_params())
153    }
154
155    /// Check if has a runtime timeout annotation.
156    #[must_use]
157    pub fn has_runtime_timeout(&self) -> bool {
158        self.items.iter().any(|a| a.is_runtime_timeout())
159    }
160
161    /// Get runtime timeout duration if present.
162    #[must_use]
163    pub fn runtime_timeout(&self) -> Option<Duration> {
164        self.items.iter().find_map(|a| a.runtime_timeout_duration())
165    }
166
167    /// Check if has a parallel annotation.
168    #[must_use]
169    pub fn has_parallel(&self) -> bool {
170        self.items.iter().any(|a| a.is_parallel())
171    }
172
173    /// Check if has an ordered annotation.
174    #[must_use]
175    pub fn has_ordered(&self) -> bool {
176        self.items.iter().any(|a| a.is_ordered())
177    }
178
179    /// Check if has a min_responses annotation.
180    #[must_use]
181    pub fn has_min_responses(&self) -> bool {
182        self.items.iter().any(|a| a.is_min_responses())
183    }
184
185    /// Get min_responses value if present.
186    #[must_use]
187    pub fn min_responses(&self) -> Option<u32> {
188        self.items.iter().find_map(|a| a.min_responses_value())
189    }
190
191    /// Get a custom annotation value by key.
192    #[must_use]
193    pub fn custom(&self, key: &str) -> Option<&str> {
194        self.items.iter().find_map(|a| a.custom_value(key))
195    }
196
197    /// Merge annotations from another set.
198    pub fn merge(&mut self, other: &Annotations) {
199        self.items.extend(other.items.iter().cloned());
200    }
201
202    /// Clear all annotations.
203    pub fn clear(&mut self) {
204        self.items.clear();
205    }
206}
207
208impl IntoIterator for Annotations {
209    type Item = ProtocolAnnotation;
210    type IntoIter = std::vec::IntoIter<ProtocolAnnotation>;
211
212    fn into_iter(self) -> Self::IntoIter {
213        self.items.into_iter()
214    }
215}
216
217impl<'a> IntoIterator for &'a Annotations {
218    type Item = &'a ProtocolAnnotation;
219    type IntoIter = std::slice::Iter<'a, ProtocolAnnotation>;
220
221    fn into_iter(self) -> Self::IntoIter {
222        self.items.iter()
223    }
224}
225
226impl FromIterator<ProtocolAnnotation> for Annotations {
227    fn from_iter<I: IntoIterator<Item = ProtocolAnnotation>>(iter: I) -> Self {
228        Self {
229            items: iter.into_iter().collect(),
230        }
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    #[test]
239    fn test_timed_choice_annotation() {
240        let ann = ProtocolAnnotation::timed_choice_ms(5000);
241        assert!(ann.is_timed_choice());
242        assert_eq!(ann.timed_choice_duration(), Some(Duration::from_secs(5)));
243    }
244
245    #[test]
246    fn test_priority_annotation() {
247        let ann = ProtocolAnnotation::priority(10);
248        assert!(ann.is_priority());
249        assert_eq!(ann.priority_value(), Some(10));
250    }
251
252    #[test]
253    fn test_custom_annotation() {
254        let ann = ProtocolAnnotation::custom("my_key", "my_value");
255        assert!(ann.is_custom_key("my_key"));
256        assert_eq!(ann.custom_value("my_key"), Some("my_value"));
257        assert_eq!(ann.custom_value("other"), None);
258    }
259
260    #[test]
261    fn test_parse_dsl_entry() {
262        let ann = ProtocolAnnotation::parse_dsl_entry("priority", "5");
263        assert_eq!(ann, ProtocolAnnotation::Priority(5));
264
265        let ann = ProtocolAnnotation::parse_dsl_entry("unknown", "value");
266        assert!(matches!(ann, ProtocolAnnotation::Custom { .. }));
267    }
268
269    #[test]
270    fn test_annotations_collection() {
271        let mut anns = Annotations::new();
272        anns.push(ProtocolAnnotation::timed_choice_ms(1000));
273        anns.push(ProtocolAnnotation::priority(5));
274
275        assert!(anns.has_timed_choice());
276        assert_eq!(anns.timed_choice(), Some(Duration::from_secs(1)));
277        assert_eq!(anns.priority(), Some(5));
278        assert_eq!(anns.len(), 2);
279    }
280
281    #[test]
282    fn test_map_roundtrip() {
283        let mut original = HashMap::new();
284        original.insert("timed_choice".to_string(), "true".to_string());
285        original.insert("timeout_ms".to_string(), "5000".to_string());
286        original.insert("priority".to_string(), "10".to_string());
287
288        let anns = Annotations::from_dsl_map(&original);
289        assert!(anns.has_timed_choice());
290        assert_eq!(anns.timed_choice(), Some(Duration::from_secs(5)));
291        assert_eq!(anns.priority(), Some(10));
292
293        let restored = anns.dsl_map();
294        assert_eq!(restored.get("timed_choice"), Some(&"true".to_string()));
295        assert_eq!(restored.get("timeout_ms"), Some(&"5000".to_string()));
296    }
297
298    #[test]
299    fn test_parallel_annotation() {
300        let ann = ProtocolAnnotation::parallel();
301        assert!(ann.is_parallel());
302        assert!(!ann.is_ordered());
303    }
304
305    #[test]
306    fn test_ordered_annotation() {
307        let ann = ProtocolAnnotation::ordered();
308        assert!(ann.is_ordered());
309        assert!(!ann.is_parallel());
310    }
311
312    #[test]
313    fn test_min_responses_annotation() {
314        let ann = ProtocolAnnotation::min_responses(3);
315        assert!(ann.is_min_responses());
316        assert_eq!(ann.min_responses_value(), Some(3));
317    }
318
319    #[test]
320    fn test_annotations_parallel_ordered() {
321        let mut anns = Annotations::new();
322        anns.push(ProtocolAnnotation::parallel());
323        anns.push(ProtocolAnnotation::min_responses(5));
324
325        assert!(anns.has_parallel());
326        assert!(!anns.has_ordered());
327        assert!(anns.has_min_responses());
328        assert_eq!(anns.min_responses(), Some(5));
329    }
330
331    #[test]
332    fn test_parse_dsl_entry_parallel() {
333        let ann = ProtocolAnnotation::parse_dsl_entry("parallel", "true");
334        assert_eq!(ann, ProtocolAnnotation::Parallel);
335
336        let ann = ProtocolAnnotation::parse_dsl_entry("ordered", "true");
337        assert_eq!(ann, ProtocolAnnotation::Ordered);
338
339        let ann = ProtocolAnnotation::parse_dsl_entry("parallel", "");
340        assert_eq!(ann, ProtocolAnnotation::Parallel);
341
342        let ann = ProtocolAnnotation::parse_dsl_entry("ordered", "");
343        assert_eq!(ann, ProtocolAnnotation::Ordered);
344
345        let ann = ProtocolAnnotation::parse_dsl_entry("min_responses", "3");
346        assert_eq!(ann, ProtocolAnnotation::MinResponses(3));
347    }
348
349    #[test]
350    fn test_to_map_new_annotations() {
351        let mut anns = Annotations::new();
352        anns.push(ProtocolAnnotation::Parallel);
353        anns.push(ProtocolAnnotation::Ordered);
354        anns.push(ProtocolAnnotation::MinResponses(5));
355
356        let map = anns.dsl_map();
357        assert_eq!(map.get("parallel"), Some(&"true".to_string()));
358        assert_eq!(map.get("ordered"), Some(&"true".to_string()));
359        assert_eq!(map.get("min_responses"), Some(&"5".to_string()));
360    }
361}