ricecoder_orchestration/
models.rs

1//! Core data models for workspace orchestration
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Represents a workspace containing multiple projects
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Workspace {
9    /// Root path of the workspace
10    pub root: PathBuf,
11
12    /// All projects in the workspace
13    pub projects: Vec<Project>,
14
15    /// Dependencies between projects
16    pub dependencies: Vec<ProjectDependency>,
17
18    /// Workspace-level configuration
19    pub config: WorkspaceConfig,
20
21    /// Workspace metrics and health indicators
22    pub metrics: WorkspaceMetrics,
23}
24
25/// Represents a single project within a workspace
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Project {
28    /// Path to the project
29    pub path: PathBuf,
30
31    /// Project name
32    pub name: String,
33
34    /// Type of project (e.g., "rust", "typescript", "python")
35    pub project_type: String,
36
37    /// Current version of the project
38    pub version: String,
39
40    /// Current status of the project
41    pub status: ProjectStatus,
42}
43
44/// Status of a project
45#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
46pub enum ProjectStatus {
47    /// Project is healthy and operational
48    Healthy,
49
50    /// Project has warnings or minor issues
51    Warning,
52
53    /// Project has critical issues
54    Critical,
55
56    /// Project status is unknown
57    Unknown,
58}
59
60/// Represents a dependency between two projects
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ProjectDependency {
63    /// Name of the project that depends on another
64    pub from: String,
65
66    /// Name of the project being depended on
67    pub to: String,
68
69    /// Type of dependency
70    pub dependency_type: DependencyType,
71
72    /// Version constraint for the dependency
73    pub version_constraint: String,
74}
75
76/// Type of dependency between projects
77#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
78pub enum DependencyType {
79    /// Direct dependency
80    Direct,
81
82    /// Transitive dependency (indirect)
83    Transitive,
84
85    /// Development-only dependency
86    Dev,
87}
88
89/// Workspace-level configuration
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct WorkspaceConfig {
92    /// Workspace rules
93    pub rules: Vec<WorkspaceRule>,
94
95    /// Additional settings as JSON
96    pub settings: serde_json::Value,
97}
98
99/// A rule that applies to the workspace
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct WorkspaceRule {
102    /// Name of the rule
103    pub name: String,
104
105    /// Type of rule
106    pub rule_type: RuleType,
107
108    /// Whether the rule is enabled
109    pub enabled: bool,
110}
111
112/// Type of workspace rule
113#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
114pub enum RuleType {
115    /// Constraint on dependencies
116    DependencyConstraint,
117
118    /// Naming convention rule
119    NamingConvention,
120
121    /// Architectural boundary rule
122    ArchitecturalBoundary,
123}
124
125/// Metrics about the workspace
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct WorkspaceMetrics {
128    /// Total number of projects
129    pub total_projects: usize,
130
131    /// Total number of dependencies
132    pub total_dependencies: usize,
133
134    /// Compliance score (0.0 to 1.0)
135    pub compliance_score: f64,
136
137    /// Overall health status
138    pub health_status: HealthStatus,
139}
140
141/// Health status of the workspace
142#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
143pub enum HealthStatus {
144    /// Workspace is healthy
145    Healthy,
146
147    /// Workspace has warnings
148    Warning,
149
150    /// Workspace has critical issues
151    Critical,
152}
153
154/// Report of impact analysis for a change
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct ImpactReport {
157    /// Unique identifier for the change
158    pub change_id: String,
159
160    /// Projects affected by the change
161    pub affected_projects: Vec<String>,
162
163    /// Level of impact
164    pub impact_level: ImpactLevel,
165
166    /// Detailed impact information
167    pub details: Vec<ImpactDetail>,
168}
169
170/// Level of impact a change has
171#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
172pub enum ImpactLevel {
173    /// Low impact
174    Low,
175
176    /// Medium impact
177    Medium,
178
179    /// High impact
180    High,
181
182    /// Critical impact
183    Critical,
184}
185
186/// Details about how a change impacts a specific project
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct ImpactDetail {
189    /// Name of the affected project
190    pub project: String,
191
192    /// Reason for the impact
193    pub reason: String,
194
195    /// Required actions to address the impact
196    pub required_actions: Vec<String>,
197}
198
199/// Represents a transaction for multi-project operations
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct Transaction {
202    /// Unique transaction identifier
203    pub id: String,
204
205    /// Operations in the transaction
206    pub operations: Vec<Operation>,
207
208    /// Current state of the transaction
209    pub state: TransactionState,
210}
211
212/// An operation within a transaction
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct Operation {
215    /// Operation identifier
216    pub id: String,
217
218    /// Project affected by the operation
219    pub project: String,
220
221    /// Type of operation
222    pub operation_type: String,
223
224    /// Operation data
225    pub data: serde_json::Value,
226}
227
228/// State of a transaction
229#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
230pub enum TransactionState {
231    /// Transaction is pending
232    Pending,
233
234    /// Transaction has been committed
235    Committed,
236
237    /// Transaction has been rolled back
238    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}