data_modelling_sdk/models/
workspace.rs

1//! Workspace model
2//!
3//! Defines the Workspace entity for the data modelling application.
4//! Workspaces are top-level containers that organize domains and their associated assets.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10/// Domain reference within a workspace
11///
12/// Contains minimal information about a domain to avoid regenerating UUIDs on each load.
13/// Full domain details are stored in separate domain.yaml files.
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
15pub struct DomainReference {
16    /// Domain identifier
17    pub id: Uuid,
18    /// Domain name (must match folder name in offline mode)
19    pub name: String,
20}
21
22/// Workspace - Top-level container for domains
23///
24/// Workspaces organize domains and their associated assets.
25/// In offline mode, each workspace corresponds to a directory containing domain folders.
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
27pub struct Workspace {
28    /// Unique identifier for the workspace
29    pub id: Uuid,
30    /// Workspace name
31    pub name: String,
32    /// Owner/creator user identifier
33    pub owner_id: Uuid,
34    /// Creation timestamp
35    pub created_at: DateTime<Utc>,
36    /// Last modification timestamp
37    pub last_modified_at: DateTime<Utc>,
38    /// Domain references
39    #[serde(default)]
40    pub domains: Vec<DomainReference>,
41}
42
43impl Workspace {
44    /// Create a new Workspace
45    pub fn new(name: String, owner_id: Uuid) -> Self {
46        let now = Utc::now();
47        Self {
48            id: Uuid::new_v4(),
49            name,
50            owner_id,
51            created_at: now,
52            last_modified_at: now,
53            domains: Vec::new(),
54        }
55    }
56
57    /// Create a workspace with a specific ID
58    pub fn with_id(id: Uuid, name: String, owner_id: Uuid) -> Self {
59        let now = Utc::now();
60        Self {
61            id,
62            name,
63            owner_id,
64            created_at: now,
65            last_modified_at: now,
66            domains: Vec::new(),
67        }
68    }
69
70    /// Add a domain reference to the workspace
71    pub fn add_domain(&mut self, domain_id: Uuid, domain_name: String) {
72        self.domains.push(DomainReference {
73            id: domain_id,
74            name: domain_name,
75        });
76        self.last_modified_at = Utc::now();
77    }
78
79    /// Remove a domain reference by ID
80    pub fn remove_domain(&mut self, domain_id: Uuid) -> bool {
81        let initial_len = self.domains.len();
82        self.domains.retain(|d| d.id != domain_id);
83        if self.domains.len() != initial_len {
84            self.last_modified_at = Utc::now();
85            true
86        } else {
87            false
88        }
89    }
90
91    /// Get a domain reference by ID
92    pub fn get_domain(&self, domain_id: Uuid) -> Option<&DomainReference> {
93        self.domains.iter().find(|d| d.id == domain_id)
94    }
95
96    /// Get a domain reference by name
97    pub fn get_domain_by_name(&self, name: &str) -> Option<&DomainReference> {
98        self.domains.iter().find(|d| d.name == name)
99    }
100
101    /// Import workspace from YAML
102    pub fn from_yaml(yaml_content: &str) -> Result<Self, serde_yaml::Error> {
103        serde_yaml::from_str(yaml_content)
104    }
105
106    /// Export workspace to YAML
107    pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
108        serde_yaml::to_string(self)
109    }
110
111    /// Import workspace from JSON
112    pub fn from_json(json_content: &str) -> Result<Self, serde_json::Error> {
113        serde_json::from_str(json_content)
114    }
115
116    /// Export workspace to JSON
117    pub fn to_json(&self) -> Result<String, serde_json::Error> {
118        serde_json::to_string(self)
119    }
120
121    /// Export workspace to pretty JSON
122    pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
123        serde_json::to_string_pretty(self)
124    }
125}
126
127impl Default for Workspace {
128    fn default() -> Self {
129        Self::new("Default Workspace".to_string(), Uuid::new_v4())
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_workspace_new() {
139        let owner_id = Uuid::new_v4();
140        let workspace = Workspace::new("Test Workspace".to_string(), owner_id);
141
142        assert_eq!(workspace.name, "Test Workspace");
143        assert_eq!(workspace.owner_id, owner_id);
144        assert!(workspace.domains.is_empty());
145    }
146
147    #[test]
148    fn test_workspace_add_domain() {
149        let mut workspace = Workspace::new("Test".to_string(), Uuid::new_v4());
150        let domain_id = Uuid::new_v4();
151
152        workspace.add_domain(domain_id, "customer-management".to_string());
153
154        assert_eq!(workspace.domains.len(), 1);
155        assert_eq!(workspace.domains[0].id, domain_id);
156        assert_eq!(workspace.domains[0].name, "customer-management");
157    }
158
159    #[test]
160    fn test_workspace_remove_domain() {
161        let mut workspace = Workspace::new("Test".to_string(), Uuid::new_v4());
162        let domain_id = Uuid::new_v4();
163        workspace.add_domain(domain_id, "test-domain".to_string());
164
165        assert!(workspace.remove_domain(domain_id));
166        assert!(workspace.domains.is_empty());
167        assert!(!workspace.remove_domain(domain_id)); // Already removed
168    }
169
170    #[test]
171    fn test_workspace_yaml_roundtrip() {
172        let mut workspace = Workspace::new("Enterprise Models".to_string(), Uuid::new_v4());
173        workspace.add_domain(Uuid::new_v4(), "finance".to_string());
174        workspace.add_domain(Uuid::new_v4(), "risk".to_string());
175
176        let yaml = workspace.to_yaml().unwrap();
177        let parsed = Workspace::from_yaml(&yaml).unwrap();
178
179        assert_eq!(workspace.id, parsed.id);
180        assert_eq!(workspace.name, parsed.name);
181        assert_eq!(workspace.domains.len(), parsed.domains.len());
182    }
183
184    #[test]
185    fn test_workspace_json_roundtrip() {
186        let workspace = Workspace::new("Test".to_string(), Uuid::new_v4());
187
188        let json = workspace.to_json().unwrap();
189        let parsed = Workspace::from_json(&json).unwrap();
190
191        assert_eq!(workspace.id, parsed.id);
192        assert_eq!(workspace.name, parsed.name);
193    }
194}