datum-core 0.9.0

Rust stream-processing library mirroring Akka/Pekko Streams Typed, built on Ractor actors
Documentation
//! Akka-style stream/stage metadata.
//!
//! [`Attributes`] is a small, immutable, cloneable bag of [`Attribute`]s attached to a stream
//! blueprint or a graph stage — a name, an input-buffer hint, or a dispatcher hint. Attributes
//! carry intent and introspection (they are surfaced to user `setup` closures and the graph
//! builder, and combined down a nested blueprint); they do not by themselves drive execution.
//!
//! Combine two bags with [`Attributes::and`]: the **argument** takes precedence over the receiver,
//! so the most-recently-applied (most specific) value wins the accessor lookups
//! ([`Attributes::name`], [`Attributes::input_buffer_hint`], [`Attributes::dispatcher_hint`]).
//!
//! Mirrors `akka.stream.Attributes`; `Attribute`/`Attributes` are re-exported from the crate root.

use std::sync::Arc;

/// A single piece of stream/stage metadata.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Attribute {
    /// Human-readable stage/stream name (used in diagnostics).
    Name(Arc<str>),
    /// Requested input-buffer sizing hint (`initial` ≤ `max`).
    InputBuffer { initial: usize, max: usize },
    /// Dispatcher name hint for where the stage's work should run.
    Dispatcher(Arc<str>),
}

/// An ordered, immutable collection of [`Attribute`]s.
///
/// Lookups return the first matching attribute; [`Attributes::and`] prepends the argument's
/// attributes so the more specific bag wins. Construction never starts any work.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Attributes {
    attribute_list: Vec<Attribute>,
}

impl Attributes {
    /// An empty attribute set (the `Default`).
    #[must_use]
    pub fn none() -> Self {
        Self::default()
    }

    /// A set holding a single [`Attribute::Name`].
    #[must_use]
    pub fn named(name: impl Into<String>) -> Self {
        Self {
            attribute_list: vec![Attribute::Name(Arc::from(name.into()))],
        }
    }

    /// A set holding a single [`Attribute::InputBuffer`] hint.
    #[must_use]
    pub fn input_buffer(initial: usize, max: usize) -> Self {
        Self {
            attribute_list: vec![Attribute::InputBuffer { initial, max }],
        }
    }

    /// A set holding a single [`Attribute::Dispatcher`] hint.
    #[must_use]
    pub fn dispatcher(name: impl Into<String>) -> Self {
        Self {
            attribute_list: vec![Attribute::Dispatcher(Arc::from(name.into()))],
        }
    }

    /// Combine two sets; the attributes of `other` take precedence on accessor lookups.
    #[must_use]
    pub fn and(mut self, other: Self) -> Self {
        if other.attribute_list.is_empty() {
            return self;
        }
        if self.attribute_list.is_empty() {
            return other;
        }
        let mut combined = other.attribute_list;
        combined.extend(self.attribute_list);
        self.attribute_list = combined;
        self
    }

    /// The underlying attributes, in lookup order (most specific first).
    #[must_use]
    pub fn attribute_list(&self) -> &[Attribute] {
        &self.attribute_list
    }

    /// The first [`Attribute::Name`], if any.
    #[must_use]
    pub fn name(&self) -> Option<&str> {
        self.attribute_list
            .iter()
            .find_map(|attribute| match attribute {
                Attribute::Name(name) => Some(name.as_ref()),
                _ => None,
            })
    }

    /// The first [`Attribute::InputBuffer`] hint as `(initial, max)`, if any.
    #[must_use]
    pub fn input_buffer_hint(&self) -> Option<(usize, usize)> {
        self.attribute_list
            .iter()
            .find_map(|attribute| match attribute {
                Attribute::InputBuffer { initial, max } => Some((*initial, *max)),
                _ => None,
            })
    }

    /// The first [`Attribute::Dispatcher`] hint, if any.
    #[must_use]
    pub fn dispatcher_hint(&self) -> Option<&str> {
        self.attribute_list
            .iter()
            .find_map(|attribute| match attribute {
                Attribute::Dispatcher(name) => Some(name.as_ref()),
                _ => None,
            })
    }

    /// Return a copy with a [`Attribute::Name`] prepended so it wins [`Attributes::name`].
    #[must_use]
    pub fn with_name(mut self, name: impl Into<String>) -> Self {
        self.attribute_list
            .insert(0, Attribute::Name(Arc::from(name.into())));
        self
    }
}