weavegraph 0.7.0

Graph-driven, concurrent agent workflow framework with versioned state, deterministic barrier merges, and rich diagnostics.
Documentation
//! ID generation utilities for run, step, node, and session identifiers.
//!
//! Supports UUID-based random generation and deterministic seeded generation
//! for testing and reproducibility.

use std::sync::atomic::{AtomicU64, Ordering};
use uuid::Uuid;

/// Controls how [`IdGenerator`] produces IDs.
#[derive(Debug, Clone, Default)]
pub struct IdConfig {
    /// When set, IDs are derived from this seed rather than a random UUID.
    pub seed: Option<u64>,
    /// Prepended to every ID produced by [`IdGenerator::generate_id`].
    pub prefix: Option<String>,
    /// Append a Unix-timestamp suffix (`-t<secs>`) to each generated ID.
    pub include_timestamp: bool,
    /// Append a monotonically increasing counter to each generated ID.
    pub use_counter: bool,
}

/// Thread-safe ID generator with configurable strategies.
///
/// # Example
///
/// ```rust
/// use weavegraph::utils::id_generator::{IdGenerator, IdConfig};
///
/// let id_gen = IdGenerator::new();
/// assert!(id_gen.generate_run_id().starts_with("run-"));
///
/// let det = IdGenerator::with_config(IdConfig {
///     seed: Some(1),
///     use_counter: true,
///     ..Default::default()
/// });
/// assert_ne!(det.generate_id(), det.generate_id());
/// ```
#[derive(Debug, Default)]
pub struct IdGenerator {
    config: IdConfig,
    counter: AtomicU64,
}

impl IdGenerator {
    /// Create a generator with default (random UUID) settings.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Create a generator with custom configuration.
    #[must_use]
    pub fn with_config(config: IdConfig) -> Self {
        Self {
            config,
            counter: AtomicU64::new(0),
        }
    }

    /// Generate an ID, applying seed, counter, timestamp, and prefix from config.
    ///
    /// ```rust
    /// use weavegraph::utils::id_generator::IdGenerator;
    ///
    /// assert!(!IdGenerator::new().generate_id().is_empty());
    /// ```
    #[must_use]
    pub fn generate_id(&self) -> String {
        let mut id = self.base_id();
        if self.config.include_timestamp {
            let ts = chrono::Utc::now().timestamp();
            id = format!("{id}-t{ts}");
        }
        if let Some(p) = &self.config.prefix {
            id = format!("{p}-{id}");
        }
        id
    }

    /// Generate an ID prefixed with `prefix`, bypassing config prefix and timestamp.
    ///
    /// ```rust
    /// use weavegraph::utils::id_generator::IdGenerator;
    ///
    /// assert!(IdGenerator::new().generate_id_with_prefix("session").starts_with("session-"));
    /// ```
    #[must_use]
    pub fn generate_id_with_prefix(&self, prefix: &str) -> String {
        format!("{prefix}-{}", self.base_id())
    }

    /// Generate a `run-<id>` identifier.
    #[must_use]
    pub fn generate_run_id(&self) -> String {
        self.generate_id_with_prefix("run")
    }

    /// Generate a `step-<id>` identifier.
    #[must_use]
    pub fn generate_step_id(&self) -> String {
        self.generate_id_with_prefix("step")
    }

    /// Generate a `node-<id>` identifier.
    #[must_use]
    pub fn generate_node_id(&self) -> String {
        self.generate_id_with_prefix("node")
    }

    /// Generate a `session-<id>` identifier.
    #[must_use]
    pub fn generate_session_id(&self) -> String {
        self.generate_id_with_prefix("session")
    }

    fn base_id(&self) -> String {
        match (self.config.seed, self.config.use_counter) {
            (Some(seed), true) => {
                let n = self.counter.fetch_add(1, Ordering::Relaxed);
                format!("seeded-{seed}-{n}")
            }
            (Some(seed), false) => format!("seeded-{seed}"),
            (None, true) => {
                let n = self.counter.fetch_add(1, Ordering::Relaxed);
                format!("counter-{n}")
            }
            (None, false) => Uuid::new_v4().to_string(),
        }
    }
}