Skip to main content

datum/
attributes.rs

1//! Akka-style stream/stage metadata.
2//!
3//! [`Attributes`] is a small, immutable, cloneable bag of [`Attribute`]s attached to a stream
4//! blueprint or a graph stage — a name, an input-buffer hint, or a dispatcher hint. Attributes
5//! carry intent and introspection (they are surfaced to user `setup` closures and the graph
6//! builder, and combined down a nested blueprint); they do not by themselves drive execution.
7//!
8//! Combine two bags with [`Attributes::and`]: the **argument** takes precedence over the receiver,
9//! so the most-recently-applied (most specific) value wins the accessor lookups
10//! ([`Attributes::name`], [`Attributes::input_buffer_hint`], [`Attributes::dispatcher_hint`]).
11//!
12//! Mirrors `akka.stream.Attributes`; `Attribute`/`Attributes` are re-exported from the crate root.
13
14use std::sync::Arc;
15
16/// A single piece of stream/stage metadata.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub enum Attribute {
19    /// Human-readable stage/stream name (used in diagnostics).
20    Name(Arc<str>),
21    /// Requested input-buffer sizing hint (`initial` ≤ `max`).
22    InputBuffer { initial: usize, max: usize },
23    /// Dispatcher name hint for where the stage's work should run.
24    Dispatcher(Arc<str>),
25}
26
27/// An ordered, immutable collection of [`Attribute`]s.
28///
29/// Lookups return the first matching attribute; [`Attributes::and`] prepends the argument's
30/// attributes so the more specific bag wins. Construction never starts any work.
31#[derive(Clone, Debug, Default, PartialEq, Eq)]
32pub struct Attributes {
33    attribute_list: Vec<Attribute>,
34}
35
36impl Attributes {
37    /// An empty attribute set (the `Default`).
38    #[must_use]
39    pub fn none() -> Self {
40        Self::default()
41    }
42
43    /// A set holding a single [`Attribute::Name`].
44    #[must_use]
45    pub fn named(name: impl Into<String>) -> Self {
46        Self {
47            attribute_list: vec![Attribute::Name(Arc::from(name.into()))],
48        }
49    }
50
51    /// A set holding a single [`Attribute::InputBuffer`] hint.
52    #[must_use]
53    pub fn input_buffer(initial: usize, max: usize) -> Self {
54        Self {
55            attribute_list: vec![Attribute::InputBuffer { initial, max }],
56        }
57    }
58
59    /// A set holding a single [`Attribute::Dispatcher`] hint.
60    #[must_use]
61    pub fn dispatcher(name: impl Into<String>) -> Self {
62        Self {
63            attribute_list: vec![Attribute::Dispatcher(Arc::from(name.into()))],
64        }
65    }
66
67    /// Combine two sets; the attributes of `other` take precedence on accessor lookups.
68    #[must_use]
69    pub fn and(mut self, other: Self) -> Self {
70        if other.attribute_list.is_empty() {
71            return self;
72        }
73        if self.attribute_list.is_empty() {
74            return other;
75        }
76        let mut combined = other.attribute_list;
77        combined.extend(self.attribute_list);
78        self.attribute_list = combined;
79        self
80    }
81
82    /// The underlying attributes, in lookup order (most specific first).
83    #[must_use]
84    pub fn attribute_list(&self) -> &[Attribute] {
85        &self.attribute_list
86    }
87
88    /// The first [`Attribute::Name`], if any.
89    #[must_use]
90    pub fn name(&self) -> Option<&str> {
91        self.attribute_list
92            .iter()
93            .find_map(|attribute| match attribute {
94                Attribute::Name(name) => Some(name.as_ref()),
95                _ => None,
96            })
97    }
98
99    /// The first [`Attribute::InputBuffer`] hint as `(initial, max)`, if any.
100    #[must_use]
101    pub fn input_buffer_hint(&self) -> Option<(usize, usize)> {
102        self.attribute_list
103            .iter()
104            .find_map(|attribute| match attribute {
105                Attribute::InputBuffer { initial, max } => Some((*initial, *max)),
106                _ => None,
107            })
108    }
109
110    /// The first [`Attribute::Dispatcher`] hint, if any.
111    #[must_use]
112    pub fn dispatcher_hint(&self) -> Option<&str> {
113        self.attribute_list
114            .iter()
115            .find_map(|attribute| match attribute {
116                Attribute::Dispatcher(name) => Some(name.as_ref()),
117                _ => None,
118            })
119    }
120
121    /// Return a copy with a [`Attribute::Name`] prepended so it wins [`Attributes::name`].
122    #[must_use]
123    pub fn with_name(mut self, name: impl Into<String>) -> Self {
124        self.attribute_list
125            .insert(0, Attribute::Name(Arc::from(name.into())));
126        self
127    }
128}