sidebyside-core 0.1.0

Core domain types for Sidebyside SDK
Documentation
//! ID types for the Sidebyside SDK
//!
//! These types provide type-safe identifiers for various entities in the system.

use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;

use crate::error::CoreError;

/// A unique identifier for a workflow
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct WorkflowId(String);

impl WorkflowId {
    /// Create a new workflow ID from a string
    ///
    /// # Errors
    /// Returns an error if the string is empty
    pub fn new(id: impl Into<String>) -> Result<Self, CoreError> {
        let id = id.into();
        if id.is_empty() {
            return Err(CoreError::InvalidId("WorkflowId cannot be empty".to_string()));
        }
        Ok(Self(id))
    }

    /// Generate a new random workflow ID
    #[must_use]
    pub fn generate() -> Self {
        Self(Uuid::new_v4().to_string())
    }

    /// Get the inner string value
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for WorkflowId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// A unique identifier for a plan
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PlanId(String);

impl PlanId {
    /// Create a new plan ID from a string
    ///
    /// # Errors
    /// Returns an error if the string is empty
    pub fn new(id: impl Into<String>) -> Result<Self, CoreError> {
        let id = id.into();
        if id.is_empty() {
            return Err(CoreError::InvalidId("PlanId cannot be empty".to_string()));
        }
        Ok(Self(id))
    }

    /// Generate a new random plan ID
    #[must_use]
    pub fn generate() -> Self {
        Self(Uuid::new_v4().to_string())
    }

    /// Get the inner string value
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for PlanId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// A unique identifier for a step within a plan
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct StepId(String);

impl StepId {
    /// Create a new step ID from a string
    ///
    /// # Errors
    /// Returns an error if the string is empty
    pub fn new(id: impl Into<String>) -> Result<Self, CoreError> {
        let id = id.into();
        if id.is_empty() {
            return Err(CoreError::InvalidId("StepId cannot be empty".to_string()));
        }
        Ok(Self(id))
    }

    /// Generate a new random step ID
    #[must_use]
    pub fn generate() -> Self {
        Self(Uuid::new_v4().to_string())
    }

    /// Get the inner string value
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for StepId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// A unique identifier for a plan execution
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ExecutionId(String);

impl ExecutionId {
    /// Create a new execution ID from a string
    ///
    /// # Errors
    /// Returns an error if the string is empty
    pub fn new(id: impl Into<String>) -> Result<Self, CoreError> {
        let id = id.into();
        if id.is_empty() {
            return Err(CoreError::InvalidId("ExecutionId cannot be empty".to_string()));
        }
        Ok(Self(id))
    }

    /// Generate a new random execution ID
    #[must_use]
    pub fn generate() -> Self {
        Self(Uuid::new_v4().to_string())
    }

    /// Get the inner string value
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for ExecutionId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// A unique identifier for a decision point
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DecisionId(String);

impl DecisionId {
    /// Create a new decision ID from a string
    ///
    /// # Errors
    /// Returns an error if the string is empty
    pub fn new(id: impl Into<String>) -> Result<Self, CoreError> {
        let id = id.into();
        if id.is_empty() {
            return Err(CoreError::InvalidId("DecisionId cannot be empty".to_string()));
        }
        Ok(Self(id))
    }

    /// Generate a new random decision ID
    #[must_use]
    pub fn generate() -> Self {
        Self(Uuid::new_v4().to_string())
    }

    /// Get the inner string value
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for DecisionId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_workflow_id_new() {
        let id = WorkflowId::new("test-workflow").unwrap();
        assert_eq!(id.as_str(), "test-workflow");
    }

    #[test]
    fn test_workflow_id_empty() {
        let result = WorkflowId::new("");
        assert!(result.is_err());
    }

    #[test]
    fn test_workflow_id_generate() {
        let id1 = WorkflowId::generate();
        let id2 = WorkflowId::generate();
        assert_ne!(id1, id2);
    }

    #[test]
    fn test_plan_id_new() {
        let id = PlanId::new("test-plan").unwrap();
        assert_eq!(id.as_str(), "test-plan");
    }

    #[test]
    fn test_step_id_new() {
        let id = StepId::new("test-step").unwrap();
        assert_eq!(id.as_str(), "test-step");
    }

    #[test]
    fn test_execution_id_new() {
        let id = ExecutionId::new("test-execution").unwrap();
        assert_eq!(id.as_str(), "test-execution");
    }

    #[test]
    fn test_decision_id_new() {
        let id = DecisionId::new("test-decision").unwrap();
        assert_eq!(id.as_str(), "test-decision");
    }
}