use super::ProtocolAnnotation;
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Annotations {
items: Vec<ProtocolAnnotation>,
}
impl Annotations {
#[must_use]
pub fn new() -> Self {
Self { items: Vec::new() }
}
#[must_use]
pub fn single(annotation: ProtocolAnnotation) -> Self {
Self {
items: vec![annotation],
}
}
#[must_use]
pub fn from_vec(items: Vec<ProtocolAnnotation>) -> Self {
Self { items }
}
pub fn from_dsl_map(map: &HashMap<String, String>) -> Self {
let mut items = Vec::new();
if map.get("timed_choice").is_some_and(|v| v == "true") {
let duration = map
.get("timeout_ms")
.and_then(|v| v.parse::<u64>().ok())
.map(Duration::from_millis)
.unwrap_or_else(|| Duration::from_secs(5));
items.push(ProtocolAnnotation::TimedChoice { duration });
}
let mut entries: Vec<_> = map.iter().collect();
entries.sort_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b));
for (key, value) in entries {
if key == "timed_choice" || key == "timeout_ms" {
continue;
}
items.push(ProtocolAnnotation::parse_dsl_entry(key, value));
}
Self { items }
}
pub fn dsl_map(&self) -> HashMap<String, String> {
let mut map = HashMap::new();
for annotation in &self.items {
for (key, value) in annotation.dsl_entries() {
map.insert(key, value);
}
}
map
}
pub fn push(&mut self, annotation: ProtocolAnnotation) {
self.items.push(annotation);
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
pub fn iter(&self) -> impl Iterator<Item = &ProtocolAnnotation> {
self.items.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ProtocolAnnotation> {
self.items.iter_mut()
}
#[must_use]
pub fn any<F>(&self, f: F) -> bool
where
F: FnMut(&ProtocolAnnotation) -> bool,
{
self.items.iter().any(f)
}
#[must_use]
pub fn find<F>(&self, f: F) -> Option<&ProtocolAnnotation>
where
F: FnMut(&&ProtocolAnnotation) -> bool,
{
self.items.iter().find(f)
}
#[must_use]
pub fn timed_choice(&self) -> Option<Duration> {
self.items.iter().find_map(|a| a.timed_choice_duration())
}
#[must_use]
pub fn has_timed_choice(&self) -> bool {
self.items.iter().any(|a| a.is_timed_choice())
}
#[must_use]
pub fn priority(&self) -> Option<u32> {
self.items.iter().find_map(|a| a.priority_value())
}
#[must_use]
pub fn is_idempotent(&self) -> bool {
self.items.iter().any(|a| a.is_idempotent())
}
#[must_use]
pub fn has_trace(&self) -> bool {
self.items.iter().any(|a| a.is_trace())
}
#[must_use]
pub fn has_heartbeat(&self) -> bool {
self.items.iter().any(|a| a.is_heartbeat())
}
#[must_use]
pub fn heartbeat(&self) -> Option<(Duration, u32)> {
self.items.iter().find_map(|a| a.heartbeat_params())
}
#[must_use]
pub fn has_runtime_timeout(&self) -> bool {
self.items.iter().any(|a| a.is_runtime_timeout())
}
#[must_use]
pub fn runtime_timeout(&self) -> Option<Duration> {
self.items.iter().find_map(|a| a.runtime_timeout_duration())
}
#[must_use]
pub fn has_parallel(&self) -> bool {
self.items.iter().any(|a| a.is_parallel())
}
#[must_use]
pub fn has_ordered(&self) -> bool {
self.items.iter().any(|a| a.is_ordered())
}
#[must_use]
pub fn has_min_responses(&self) -> bool {
self.items.iter().any(|a| a.is_min_responses())
}
#[must_use]
pub fn min_responses(&self) -> Option<u32> {
self.items.iter().find_map(|a| a.min_responses_value())
}
#[must_use]
pub fn custom(&self, key: &str) -> Option<&str> {
self.items.iter().find_map(|a| a.custom_value(key))
}
pub fn merge(&mut self, other: &Annotations) {
self.items.extend(other.items.iter().cloned());
}
pub fn clear(&mut self) {
self.items.clear();
}
}
impl IntoIterator for Annotations {
type Item = ProtocolAnnotation;
type IntoIter = std::vec::IntoIter<ProtocolAnnotation>;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
impl<'a> IntoIterator for &'a Annotations {
type Item = &'a ProtocolAnnotation;
type IntoIter = std::slice::Iter<'a, ProtocolAnnotation>;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
}
impl FromIterator<ProtocolAnnotation> for Annotations {
fn from_iter<I: IntoIterator<Item = ProtocolAnnotation>>(iter: I) -> Self {
Self {
items: iter.into_iter().collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timed_choice_annotation() {
let ann = ProtocolAnnotation::timed_choice_ms(5000);
assert!(ann.is_timed_choice());
assert_eq!(ann.timed_choice_duration(), Some(Duration::from_secs(5)));
}
#[test]
fn test_priority_annotation() {
let ann = ProtocolAnnotation::priority(10);
assert!(ann.is_priority());
assert_eq!(ann.priority_value(), Some(10));
}
#[test]
fn test_custom_annotation() {
let ann = ProtocolAnnotation::custom("my_key", "my_value");
assert!(ann.is_custom_key("my_key"));
assert_eq!(ann.custom_value("my_key"), Some("my_value"));
assert_eq!(ann.custom_value("other"), None);
}
#[test]
fn test_parse_dsl_entry() {
let ann = ProtocolAnnotation::parse_dsl_entry("priority", "5");
assert_eq!(ann, ProtocolAnnotation::Priority(5));
let ann = ProtocolAnnotation::parse_dsl_entry("unknown", "value");
assert!(matches!(ann, ProtocolAnnotation::Custom { .. }));
}
#[test]
fn test_annotations_collection() {
let mut anns = Annotations::new();
anns.push(ProtocolAnnotation::timed_choice_ms(1000));
anns.push(ProtocolAnnotation::priority(5));
assert!(anns.has_timed_choice());
assert_eq!(anns.timed_choice(), Some(Duration::from_secs(1)));
assert_eq!(anns.priority(), Some(5));
assert_eq!(anns.len(), 2);
}
#[test]
fn test_map_roundtrip() {
let mut original = HashMap::new();
original.insert("timed_choice".to_string(), "true".to_string());
original.insert("timeout_ms".to_string(), "5000".to_string());
original.insert("priority".to_string(), "10".to_string());
let anns = Annotations::from_dsl_map(&original);
assert!(anns.has_timed_choice());
assert_eq!(anns.timed_choice(), Some(Duration::from_secs(5)));
assert_eq!(anns.priority(), Some(10));
let restored = anns.dsl_map();
assert_eq!(restored.get("timed_choice"), Some(&"true".to_string()));
assert_eq!(restored.get("timeout_ms"), Some(&"5000".to_string()));
}
#[test]
fn test_parallel_annotation() {
let ann = ProtocolAnnotation::parallel();
assert!(ann.is_parallel());
assert!(!ann.is_ordered());
}
#[test]
fn test_ordered_annotation() {
let ann = ProtocolAnnotation::ordered();
assert!(ann.is_ordered());
assert!(!ann.is_parallel());
}
#[test]
fn test_min_responses_annotation() {
let ann = ProtocolAnnotation::min_responses(3);
assert!(ann.is_min_responses());
assert_eq!(ann.min_responses_value(), Some(3));
}
#[test]
fn test_annotations_parallel_ordered() {
let mut anns = Annotations::new();
anns.push(ProtocolAnnotation::parallel());
anns.push(ProtocolAnnotation::min_responses(5));
assert!(anns.has_parallel());
assert!(!anns.has_ordered());
assert!(anns.has_min_responses());
assert_eq!(anns.min_responses(), Some(5));
}
#[test]
fn test_parse_dsl_entry_parallel() {
let ann = ProtocolAnnotation::parse_dsl_entry("parallel", "true");
assert_eq!(ann, ProtocolAnnotation::Parallel);
let ann = ProtocolAnnotation::parse_dsl_entry("ordered", "true");
assert_eq!(ann, ProtocolAnnotation::Ordered);
let ann = ProtocolAnnotation::parse_dsl_entry("parallel", "");
assert_eq!(ann, ProtocolAnnotation::Parallel);
let ann = ProtocolAnnotation::parse_dsl_entry("ordered", "");
assert_eq!(ann, ProtocolAnnotation::Ordered);
let ann = ProtocolAnnotation::parse_dsl_entry("min_responses", "3");
assert_eq!(ann, ProtocolAnnotation::MinResponses(3));
}
#[test]
fn test_to_map_new_annotations() {
let mut anns = Annotations::new();
anns.push(ProtocolAnnotation::Parallel);
anns.push(ProtocolAnnotation::Ordered);
anns.push(ProtocolAnnotation::MinResponses(5));
let map = anns.dsl_map();
assert_eq!(map.get("parallel"), Some(&"true".to_string()));
assert_eq!(map.get("ordered"), Some(&"true".to_string()));
assert_eq!(map.get("min_responses"), Some(&"5".to_string()));
}
}