1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Workspace {
9 pub root: PathBuf,
11
12 pub projects: Vec<Project>,
14
15 pub dependencies: Vec<ProjectDependency>,
17
18 pub config: WorkspaceConfig,
20
21 pub metrics: WorkspaceMetrics,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Project {
28 pub path: PathBuf,
30
31 pub name: String,
33
34 pub project_type: String,
36
37 pub version: String,
39
40 pub status: ProjectStatus,
42}
43
44#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
46pub enum ProjectStatus {
47 Healthy,
49
50 Warning,
52
53 Critical,
55
56 Unknown,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ProjectDependency {
63 pub from: String,
65
66 pub to: String,
68
69 pub dependency_type: DependencyType,
71
72 pub version_constraint: String,
74}
75
76#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
78pub enum DependencyType {
79 Direct,
81
82 Transitive,
84
85 Dev,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct WorkspaceConfig {
92 pub rules: Vec<WorkspaceRule>,
94
95 pub settings: serde_json::Value,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct WorkspaceRule {
102 pub name: String,
104
105 pub rule_type: RuleType,
107
108 pub enabled: bool,
110}
111
112#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
114pub enum RuleType {
115 DependencyConstraint,
117
118 NamingConvention,
120
121 ArchitecturalBoundary,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct WorkspaceMetrics {
128 pub total_projects: usize,
130
131 pub total_dependencies: usize,
133
134 pub compliance_score: f64,
136
137 pub health_status: HealthStatus,
139}
140
141#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
143pub enum HealthStatus {
144 Healthy,
146
147 Warning,
149
150 Critical,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct ImpactReport {
157 pub change_id: String,
159
160 pub affected_projects: Vec<String>,
162
163 pub impact_level: ImpactLevel,
165
166 pub details: Vec<ImpactDetail>,
168}
169
170#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
172pub enum ImpactLevel {
173 Low,
175
176 Medium,
178
179 High,
181
182 Critical,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct ImpactDetail {
189 pub project: String,
191
192 pub reason: String,
194
195 pub required_actions: Vec<String>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct Transaction {
202 pub id: String,
204
205 pub operations: Vec<Operation>,
207
208 pub state: TransactionState,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct Operation {
215 pub id: String,
217
218 pub project: String,
220
221 pub operation_type: String,
223
224 pub data: serde_json::Value,
226}
227
228#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
230pub enum TransactionState {
231 Pending,
233
234 Committed,
236
237 RolledBack,
239}
240
241impl Default for Workspace {
242 fn default() -> Self {
243 Self {
244 root: PathBuf::new(),
245 projects: Vec::new(),
246 dependencies: Vec::new(),
247 config: WorkspaceConfig::default(),
248 metrics: WorkspaceMetrics::default(),
249 }
250 }
251}
252
253impl Default for WorkspaceConfig {
254 fn default() -> Self {
255 Self {
256 rules: Vec::new(),
257 settings: serde_json::json!({}),
258 }
259 }
260}
261
262impl Default for WorkspaceMetrics {
263 fn default() -> Self {
264 Self {
265 total_projects: 0,
266 total_dependencies: 0,
267 compliance_score: 1.0,
268 health_status: HealthStatus::Healthy,
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_project_creation() {
279 let project = Project {
280 path: PathBuf::from("/path/to/project"),
281 name: "test-project".to_string(),
282 project_type: "rust".to_string(),
283 version: "0.1.0".to_string(),
284 status: ProjectStatus::Healthy,
285 };
286
287 assert_eq!(project.name, "test-project");
288 assert_eq!(project.project_type, "rust");
289 assert_eq!(project.status, ProjectStatus::Healthy);
290 }
291
292 #[test]
293 fn test_project_dependency_creation() {
294 let dep = ProjectDependency {
295 from: "project-a".to_string(),
296 to: "project-b".to_string(),
297 dependency_type: DependencyType::Direct,
298 version_constraint: "^0.1.0".to_string(),
299 };
300
301 assert_eq!(dep.from, "project-a");
302 assert_eq!(dep.to, "project-b");
303 assert_eq!(dep.dependency_type, DependencyType::Direct);
304 }
305
306 #[test]
307 fn test_workspace_default() {
308 let workspace = Workspace::default();
309
310 assert_eq!(workspace.projects.len(), 0);
311 assert_eq!(workspace.dependencies.len(), 0);
312 assert_eq!(workspace.metrics.total_projects, 0);
313 assert_eq!(workspace.metrics.health_status, HealthStatus::Healthy);
314 }
315
316 #[test]
317 fn test_workspace_rule_creation() {
318 let rule = WorkspaceRule {
319 name: "no-circular-deps".to_string(),
320 rule_type: RuleType::DependencyConstraint,
321 enabled: true,
322 };
323
324 assert_eq!(rule.name, "no-circular-deps");
325 assert_eq!(rule.rule_type, RuleType::DependencyConstraint);
326 assert!(rule.enabled);
327 }
328
329 #[test]
330 fn test_impact_report_creation() {
331 let report = ImpactReport {
332 change_id: "change-123".to_string(),
333 affected_projects: vec!["project-a".to_string(), "project-b".to_string()],
334 impact_level: ImpactLevel::High,
335 details: vec![],
336 };
337
338 assert_eq!(report.change_id, "change-123");
339 assert_eq!(report.affected_projects.len(), 2);
340 assert_eq!(report.impact_level, ImpactLevel::High);
341 }
342
343 #[test]
344 fn test_transaction_creation() {
345 let transaction = Transaction {
346 id: "txn-123".to_string(),
347 operations: vec![],
348 state: TransactionState::Pending,
349 };
350
351 assert_eq!(transaction.id, "txn-123");
352 assert_eq!(transaction.state, TransactionState::Pending);
353 }
354
355 #[test]
356 fn test_serialization_project() {
357 let project = Project {
358 path: PathBuf::from("/path/to/project"),
359 name: "test-project".to_string(),
360 project_type: "rust".to_string(),
361 version: "0.1.0".to_string(),
362 status: ProjectStatus::Healthy,
363 };
364
365 let json = serde_json::to_string(&project).expect("serialization failed");
366 let deserialized: Project =
367 serde_json::from_str(&json).expect("deserialization failed");
368
369 assert_eq!(project.name, deserialized.name);
370 assert_eq!(project.project_type, deserialized.project_type);
371 }
372
373 #[test]
374 fn test_serialization_workspace() {
375 let workspace = Workspace::default();
376
377 let json = serde_json::to_string(&workspace).expect("serialization failed");
378 let deserialized: Workspace =
379 serde_json::from_str(&json).expect("deserialization failed");
380
381 assert_eq!(workspace.projects.len(), deserialized.projects.len());
382 assert_eq!(workspace.dependencies.len(), deserialized.dependencies.len());
383 }
384
385 #[test]
386 fn test_project_status_variants() {
387 assert_eq!(ProjectStatus::Healthy, ProjectStatus::Healthy);
388 assert_ne!(ProjectStatus::Healthy, ProjectStatus::Warning);
389 assert_ne!(ProjectStatus::Warning, ProjectStatus::Critical);
390 assert_ne!(ProjectStatus::Critical, ProjectStatus::Unknown);
391 }
392
393 #[test]
394 fn test_dependency_type_variants() {
395 assert_eq!(DependencyType::Direct, DependencyType::Direct);
396 assert_ne!(DependencyType::Direct, DependencyType::Transitive);
397 assert_ne!(DependencyType::Transitive, DependencyType::Dev);
398 }
399
400 #[test]
401 fn test_health_status_variants() {
402 assert_eq!(HealthStatus::Healthy, HealthStatus::Healthy);
403 assert_ne!(HealthStatus::Healthy, HealthStatus::Warning);
404 assert_ne!(HealthStatus::Warning, HealthStatus::Critical);
405 }
406
407 #[test]
408 fn test_impact_level_variants() {
409 assert_eq!(ImpactLevel::Low, ImpactLevel::Low);
410 assert_ne!(ImpactLevel::Low, ImpactLevel::Medium);
411 assert_ne!(ImpactLevel::Medium, ImpactLevel::High);
412 assert_ne!(ImpactLevel::High, ImpactLevel::Critical);
413 }
414
415 #[test]
416 fn test_transaction_state_variants() {
417 assert_eq!(TransactionState::Pending, TransactionState::Pending);
418 assert_ne!(TransactionState::Pending, TransactionState::Committed);
419 assert_ne!(TransactionState::Committed, TransactionState::RolledBack);
420 }
421
422 #[test]
423 fn test_rule_type_variants() {
424 assert_eq!(RuleType::DependencyConstraint, RuleType::DependencyConstraint);
425 assert_ne!(RuleType::DependencyConstraint, RuleType::NamingConvention);
426 assert_ne!(RuleType::NamingConvention, RuleType::ArchitecturalBoundary);
427 }
428
429 #[test]
430 fn test_workspace_with_projects() {
431 let mut workspace = Workspace::default();
432 workspace.projects.push(Project {
433 path: PathBuf::from("/path/to/project1"),
434 name: "project1".to_string(),
435 project_type: "rust".to_string(),
436 version: "0.1.0".to_string(),
437 status: ProjectStatus::Healthy,
438 });
439
440 assert_eq!(workspace.projects.len(), 1);
441 assert_eq!(workspace.projects[0].name, "project1");
442 }
443
444 #[test]
445 fn test_workspace_with_dependencies() {
446 let mut workspace = Workspace::default();
447 workspace.dependencies.push(ProjectDependency {
448 from: "project-a".to_string(),
449 to: "project-b".to_string(),
450 dependency_type: DependencyType::Direct,
451 version_constraint: "^0.1.0".to_string(),
452 });
453
454 assert_eq!(workspace.dependencies.len(), 1);
455 assert_eq!(workspace.dependencies[0].from, "project-a");
456 }
457
458 #[test]
459 fn test_workspace_metrics_update() {
460 let mut metrics = WorkspaceMetrics::default();
461 metrics.total_projects = 10;
462 metrics.total_dependencies = 15;
463 metrics.compliance_score = 0.95;
464 metrics.health_status = HealthStatus::Warning;
465
466 assert_eq!(metrics.total_projects, 10);
467 assert_eq!(metrics.total_dependencies, 15);
468 assert_eq!(metrics.compliance_score, 0.95);
469 assert_eq!(metrics.health_status, HealthStatus::Warning);
470 }
471
472 #[test]
473 fn test_impact_detail_creation() {
474 let detail = ImpactDetail {
475 project: "project-a".to_string(),
476 reason: "Breaking API change".to_string(),
477 required_actions: vec![
478 "Update imports".to_string(),
479 "Run tests".to_string(),
480 ],
481 };
482
483 assert_eq!(detail.project, "project-a");
484 assert_eq!(detail.reason, "Breaking API change");
485 assert_eq!(detail.required_actions.len(), 2);
486 }
487
488 #[test]
489 fn test_operation_creation() {
490 let op = Operation {
491 id: "op-123".to_string(),
492 project: "project-a".to_string(),
493 operation_type: "update".to_string(),
494 data: serde_json::json!({"version": "0.2.0"}),
495 };
496
497 assert_eq!(op.id, "op-123");
498 assert_eq!(op.project, "project-a");
499 assert_eq!(op.operation_type, "update");
500 }
501
502 #[test]
503 fn test_workspace_config_with_rules() {
504 let mut config = WorkspaceConfig::default();
505 config.rules.push(WorkspaceRule {
506 name: "no-circular-deps".to_string(),
507 rule_type: RuleType::DependencyConstraint,
508 enabled: true,
509 });
510
511 assert_eq!(config.rules.len(), 1);
512 assert_eq!(config.rules[0].name, "no-circular-deps");
513 }
514}