use std::fmt::{Display, Formatter, Result as FmtResult};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TaskNamespace {
pub tenant_id: String,
pub package_name: String,
pub workflow_id: String,
pub task_id: String,
}
impl TaskNamespace {
pub fn new(tenant_id: &str, package_name: &str, workflow_id: &str, task_id: &str) -> Self {
Self {
tenant_id: tenant_id.to_string(),
package_name: package_name.to_string(),
workflow_id: workflow_id.to_string(),
task_id: task_id.to_string(),
}
}
pub fn from_string(namespace_str: &str) -> Result<Self, String> {
parse_namespace(namespace_str)
}
pub fn is_public(&self) -> bool {
self.tenant_id == "public"
}
pub fn is_embedded(&self) -> bool {
self.package_name == "embedded"
}
}
impl Display for TaskNamespace {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(
f,
"{}::{}::{}::{}",
self.tenant_id, self.package_name, self.workflow_id, self.task_id
)
}
}
pub fn parse_namespace(namespace_str: &str) -> Result<TaskNamespace, String> {
let parts: Vec<&str> = namespace_str.split("::").collect();
if parts.len() != 4 {
return Err(format!(
"Invalid namespace format '{}'. Expected 'tenant::package::workflow::task'",
namespace_str
));
}
Ok(TaskNamespace::new(parts[0], parts[1], parts[2], parts[3]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_embedded_namespace() {
let ns = TaskNamespace::new("public", "embedded", "customer_etl", "extract_data");
assert_eq!(ns.tenant_id, "public");
assert_eq!(ns.package_name, "embedded");
assert_eq!(ns.workflow_id, "customer_etl");
assert_eq!(ns.task_id, "extract_data");
assert!(ns.is_public());
assert!(ns.is_embedded());
}
#[test]
fn test_packaged_namespace() {
let ns = TaskNamespace::new("public", "analytics.so", "data_pipeline", "extract_data");
assert_eq!(ns.tenant_id, "public");
assert_eq!(ns.package_name, "analytics.so");
assert_eq!(ns.workflow_id, "data_pipeline");
assert_eq!(ns.task_id, "extract_data");
assert!(ns.is_public());
assert!(!ns.is_embedded());
}
#[test]
fn test_tenant_namespace() {
let ns = TaskNamespace::new(
"customer_123",
"embedded",
"order_processing",
"validate_order",
);
assert_eq!(ns.tenant_id, "customer_123");
assert_eq!(ns.package_name, "embedded");
assert_eq!(ns.workflow_id, "order_processing");
assert_eq!(ns.task_id, "validate_order");
assert!(!ns.is_public());
assert!(ns.is_embedded());
}
#[test]
fn test_namespace_display() {
let ns = TaskNamespace::new("public", "embedded", "etl", "extract");
assert_eq!(ns.to_string(), "public::embedded::etl::extract");
let ns = TaskNamespace::new("tenant_1", "pkg.so", "analytics", "process");
assert_eq!(ns.to_string(), "tenant_1::pkg.so::analytics::process");
}
#[test]
fn test_namespace_equality_and_hashing() {
let ns1 = TaskNamespace::new("public", "embedded", "etl", "extract");
let ns2 = TaskNamespace::new("public", "embedded", "etl", "extract");
let ns3 = TaskNamespace::new("public", "embedded", "etl", "transform");
assert_eq!(ns1, ns2);
assert_ne!(ns1, ns3);
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert(ns1.clone(), "task1");
map.insert(ns3.clone(), "task2");
assert_eq!(map.get(&ns2), Some(&"task1"));
assert_eq!(map.len(), 2);
}
#[test]
fn test_parse_namespace() {
let ns = parse_namespace("public::embedded::etl::extract").unwrap();
assert_eq!(ns.tenant_id, "public");
assert_eq!(ns.package_name, "embedded");
assert_eq!(ns.workflow_id, "etl");
assert_eq!(ns.task_id, "extract");
assert!(parse_namespace("invalid").is_err());
assert!(parse_namespace("a::b::c").is_err());
assert!(parse_namespace("a::b::c::d::e").is_err());
}
#[test]
fn test_from_string() {
let ns = TaskNamespace::from_string("tenant::pkg::wf::task").unwrap();
assert_eq!(ns.tenant_id, "tenant");
assert_eq!(ns.task_id, "task");
assert!(TaskNamespace::from_string("invalid").is_err());
}
}