use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct DomainOwner {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub team: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ViewPosition {
pub x: f64,
pub y: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DomainConfig {
pub id: Uuid,
pub workspace_id: Uuid,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub created_at: DateTime<Utc>,
pub last_modified_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner: Option<DomainOwner>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub systems: Vec<Uuid>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tables: Vec<Uuid>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub products: Vec<Uuid>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub assets: Vec<Uuid>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub processes: Vec<Uuid>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub decisions: Vec<Uuid>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub view_positions: HashMap<String, HashMap<String, ViewPosition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub folder_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_path: Option<String>,
}
impl DomainConfig {
pub fn new(name: String, workspace_id: Uuid) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4(),
workspace_id,
name,
description: None,
created_at: now,
last_modified_at: now,
owner: None,
systems: Vec::new(),
tables: Vec::new(),
products: Vec::new(),
assets: Vec::new(),
processes: Vec::new(),
decisions: Vec::new(),
view_positions: HashMap::new(),
folder_path: None,
workspace_path: None,
}
}
pub fn with_id(id: Uuid, name: String, workspace_id: Uuid) -> Self {
let now = Utc::now();
Self {
id,
workspace_id,
name,
description: None,
created_at: now,
last_modified_at: now,
owner: None,
systems: Vec::new(),
tables: Vec::new(),
products: Vec::new(),
assets: Vec::new(),
processes: Vec::new(),
decisions: Vec::new(),
view_positions: HashMap::new(),
folder_path: None,
workspace_path: None,
}
}
pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
pub fn with_owner(mut self, owner: DomainOwner) -> Self {
self.owner = Some(owner);
self
}
pub fn add_table(&mut self, table_id: Uuid) {
if !self.tables.contains(&table_id) {
self.tables.push(table_id);
self.last_modified_at = Utc::now();
}
}
pub fn remove_table(&mut self, table_id: Uuid) -> bool {
let initial_len = self.tables.len();
self.tables.retain(|&id| id != table_id);
if self.tables.len() != initial_len {
self.last_modified_at = Utc::now();
true
} else {
false
}
}
pub fn add_product(&mut self, product_id: Uuid) {
if !self.products.contains(&product_id) {
self.products.push(product_id);
self.last_modified_at = Utc::now();
}
}
pub fn add_asset(&mut self, asset_id: Uuid) {
if !self.assets.contains(&asset_id) {
self.assets.push(asset_id);
self.last_modified_at = Utc::now();
}
}
pub fn add_process(&mut self, process_id: Uuid) {
if !self.processes.contains(&process_id) {
self.processes.push(process_id);
self.last_modified_at = Utc::now();
}
}
pub fn add_decision(&mut self, decision_id: Uuid) {
if !self.decisions.contains(&decision_id) {
self.decisions.push(decision_id);
self.last_modified_at = Utc::now();
}
}
pub fn add_system(&mut self, system_id: Uuid) {
if !self.systems.contains(&system_id) {
self.systems.push(system_id);
self.last_modified_at = Utc::now();
}
}
pub fn set_view_position(&mut self, view_mode: &str, entity_id: &str, x: f64, y: f64) {
let positions = self
.view_positions
.entry(view_mode.to_string())
.or_default();
positions.insert(entity_id.to_string(), ViewPosition { x, y });
self.last_modified_at = Utc::now();
}
pub fn get_view_position(&self, view_mode: &str, entity_id: &str) -> Option<&ViewPosition> {
self.view_positions
.get(view_mode)
.and_then(|positions| positions.get(entity_id))
}
pub fn from_yaml(yaml_content: &str) -> Result<Self, serde_yaml::Error> {
serde_yaml::from_str(yaml_content)
}
pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
serde_yaml::to_string(self)
}
pub fn from_json(json_content: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json_content)
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_domain_config_new() {
let workspace_id = Uuid::new_v4();
let config = DomainConfig::new("Customer Management".to_string(), workspace_id);
assert_eq!(config.name, "Customer Management");
assert_eq!(config.workspace_id, workspace_id);
assert!(config.tables.is_empty());
assert!(config.products.is_empty());
}
#[test]
fn test_domain_config_add_table() {
let mut config = DomainConfig::new("Test".to_string(), Uuid::new_v4());
let table_id = Uuid::new_v4();
config.add_table(table_id);
assert_eq!(config.tables.len(), 1);
assert_eq!(config.tables[0], table_id);
config.add_table(table_id);
assert_eq!(config.tables.len(), 1);
}
#[test]
fn test_domain_config_view_positions() {
let mut config = DomainConfig::new("Test".to_string(), Uuid::new_v4());
let entity_id = Uuid::new_v4().to_string();
config.set_view_position("systems", &entity_id, 100.0, 200.0);
let pos = config.get_view_position("systems", &entity_id);
assert!(pos.is_some());
let pos = pos.unwrap();
assert_eq!(pos.x, 100.0);
assert_eq!(pos.y, 200.0);
}
#[test]
fn test_domain_config_yaml_roundtrip() {
let workspace_id = Uuid::new_v4();
let mut config = DomainConfig::new("Finance".to_string(), workspace_id);
config.description = Some("Financial data domain".to_string());
config.owner = Some(DomainOwner {
name: Some("Jane Doe".to_string()),
email: Some("jane@example.com".to_string()),
team: Some("Data Team".to_string()),
role: Some("Data Owner".to_string()),
});
config.add_table(Uuid::new_v4());
config.add_product(Uuid::new_v4());
let yaml = config.to_yaml().unwrap();
let parsed = DomainConfig::from_yaml(&yaml).unwrap();
assert_eq!(config.id, parsed.id);
assert_eq!(config.name, parsed.name);
assert_eq!(config.description, parsed.description);
assert_eq!(config.tables.len(), parsed.tables.len());
}
#[test]
fn test_domain_config_json_roundtrip() {
let config = DomainConfig::new("Test".to_string(), Uuid::new_v4());
let json = config.to_json().unwrap();
let parsed = DomainConfig::from_json(&json).unwrap();
assert_eq!(config.id, parsed.id);
assert_eq!(config.name, parsed.name);
}
}