1use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::path::PathBuf;
10use thiserror::Error;
11use tokio::fs;
12
13pub mod analyzers;
14pub mod converters;
15pub mod executors;
16pub mod planners;
17pub mod validators;
18
19#[derive(Error, Debug)]
21pub enum MigrationError {
22 #[error("Legacy system analysis failed: {0}")]
23 AnalysisError(String),
24
25 #[error("Migration plan generation failed: {0}")]
26 PlanningError(String),
27
28 #[error("Migration execution failed: {0}")]
29 ExecutionError(String),
30
31 #[error("Migration validation failed: {0}")]
32 ValidationError(String),
33
34 #[error("Rollback operation failed: {0}")]
35 RollbackError(String),
36
37 #[error("I/O error: {0}")]
38 IoError(#[from] std::io::Error),
39
40 #[error("Serialization error: {0}")]
41 SerializationError(#[from] serde_json::Error),
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46pub enum LegacySystemType {
47 PermissionBased,
49 BasicRbac,
51 Abac,
53 Custom(String),
55 Hybrid(Vec<LegacySystemType>),
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct LegacyRole {
62 pub id: String,
63 pub name: String,
64 pub description: Option<String>,
65 pub permissions: Vec<String>,
66 pub parent_roles: Vec<String>,
67 pub metadata: HashMap<String, String>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct LegacyUserAssignment {
73 pub user_id: String,
74 pub role_id: Option<String>,
75 pub permissions: Vec<String>,
76 pub attributes: HashMap<String, String>,
77 pub expiration: Option<chrono::DateTime<chrono::Utc>>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct LegacyPermission {
83 pub id: String,
84 pub action: String,
85 pub resource: String,
86 pub conditions: HashMap<String, String>,
87 pub metadata: HashMap<String, String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct LegacySystemAnalysis {
93 pub system_type: LegacySystemType,
95
96 pub role_count: usize,
98
99 pub permission_count: usize,
101
102 pub user_assignment_count: usize,
104
105 pub roles: Vec<LegacyRole>,
107
108 pub permissions: Vec<LegacyPermission>,
110
111 pub user_assignments: Vec<LegacyUserAssignment>,
113
114 pub hierarchy_depth: usize,
116
117 pub duplicates_found: bool,
119
120 pub orphaned_permissions: Vec<String>,
122
123 pub circular_dependencies: Vec<Vec<String>>,
125
126 pub custom_attributes: HashSet<String>,
128
129 pub complexity_score: u8,
131
132 pub recommended_strategy: MigrationStrategy,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
138pub enum MigrationStrategy {
139 DirectMapping,
141 GradualMigration,
143 Rebuild,
145 Custom(String),
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct MigrationPlan {
152 pub id: String,
154
155 pub source_analysis: LegacySystemAnalysis,
157
158 pub strategy: MigrationStrategy,
160
161 pub phases: Vec<MigrationPhase>,
163
164 pub role_mappings: HashMap<String, String>,
166
167 pub permission_mappings: HashMap<String, String>,
169
170 pub user_migrations: Vec<UserMigration>,
172
173 pub pre_validation_steps: Vec<ValidationStep>,
175
176 pub post_validation_steps: Vec<ValidationStep>,
178
179 pub rollback_plan: RollbackPlan,
181
182 pub estimated_duration: chrono::Duration,
184
185 pub risk_level: RiskLevel,
187
188 pub downtime_required: Option<chrono::Duration>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct MigrationPhase {
195 pub id: String,
196 pub name: String,
197 pub description: String,
198 pub order: u32,
199 pub operations: Vec<MigrationOperation>,
200 pub dependencies: Vec<String>,
201 pub estimated_duration: chrono::Duration,
202 pub rollback_operations: Vec<MigrationOperation>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub enum MigrationOperation {
208 CreateRole {
209 role_id: String,
210 name: String,
211 description: Option<String>,
212 permissions: Vec<String>,
213 parent_role: Option<String>,
214 },
215 AssignUserRole {
216 user_id: String,
217 role_id: String,
218 expiration: Option<chrono::DateTime<chrono::Utc>>,
219 },
220 CreatePermission {
221 permission_id: String,
222 action: String,
223 resource: String,
224 conditions: HashMap<String, String>,
225 },
226 MigrateCustomAttribute {
227 attribute_name: String,
228 conversion_logic: String,
229 },
230 ValidateIntegrity {
231 validation_type: String,
232 parameters: HashMap<String, String>,
233 },
234 Backup {
235 backup_location: PathBuf,
236 backup_type: BackupType,
237 },
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct UserMigration {
243 pub user_id: String,
244 pub legacy_roles: Vec<String>,
245 pub legacy_permissions: Vec<String>,
246 pub new_roles: Vec<String>,
247 pub migration_notes: Option<String>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct ValidationStep {
253 pub id: String,
254 pub name: String,
255 pub description: String,
256 pub validation_type: ValidationType,
257 pub parameters: HashMap<String, String>,
258 pub required: bool,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
263pub enum ValidationType {
264 HierarchyIntegrity,
266 PermissionConsistency,
268 UserAssignmentValidity,
270 PrivilegeEscalationCheck,
272 Custom(String),
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct RollbackPlan {
279 pub phases: Vec<RollbackPhase>,
280 pub backup_locations: Vec<PathBuf>,
281 pub recovery_time_objective: chrono::Duration,
282 pub manual_steps: Vec<String>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct RollbackPhase {
288 pub id: String,
289 pub name: String,
290 pub operations: Vec<MigrationOperation>,
291 pub order: u32,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
296pub enum RiskLevel {
297 Low,
298 Medium,
299 High,
300 Critical,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub enum BackupType {
306 Full,
308 Incremental,
310 ConfigOnly,
312 DataOnly,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct MigrationResult {
319 pub plan_id: String,
320 pub status: MigrationStatus,
321 pub started_at: chrono::DateTime<chrono::Utc>,
322 pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
323 pub phases_completed: Vec<String>,
324 pub current_phase: Option<String>,
325 pub errors: Vec<String>,
326 pub warnings: Vec<String>,
327 pub metrics: MigrationMetrics,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
332pub enum MigrationStatus {
333 Planned,
334 InProgress,
335 Completed,
336 Failed,
337 RolledBack,
338 Paused,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct MigrationMetrics {
344 pub roles_migrated: usize,
345 pub permissions_migrated: usize,
346 pub users_migrated: usize,
347 pub errors_encountered: usize,
348 pub warnings_generated: usize,
349 pub validation_failures: usize,
350 pub rollback_count: usize,
351}
352
353pub struct MigrationManager {
355 config: MigrationConfig,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct MigrationConfig {
362 pub working_directory: PathBuf,
364
365 pub backup_directory: PathBuf,
367
368 pub max_concurrent_operations: usize,
370
371 pub operation_timeout: chrono::Duration,
373
374 pub dry_run: bool,
376
377 pub verbose: bool,
379}
380
381impl Default for MigrationConfig {
382 fn default() -> Self {
383 Self {
384 working_directory: PathBuf::from("./migration"),
385 backup_directory: PathBuf::from("./migration/backups"),
386 max_concurrent_operations: 4,
387 operation_timeout: chrono::Duration::minutes(30),
388 dry_run: false,
389 verbose: false,
390 }
391 }
392}
393
394impl MigrationConfig {
395 pub fn dry_run() -> Self {
399 Self {
400 dry_run: true,
401 verbose: true,
402 ..Default::default()
403 }
404 }
405
406 pub fn conservative() -> Self {
410 Self {
411 max_concurrent_operations: 1,
412 operation_timeout: chrono::Duration::minutes(120),
413 verbose: true,
414 ..Default::default()
415 }
416 }
417
418 pub fn aggressive() -> Self {
420 Self {
421 max_concurrent_operations: 16,
422 operation_timeout: chrono::Duration::minutes(60),
423 ..Default::default()
424 }
425 }
426
427 pub fn builder() -> MigrationConfigBuilder {
429 MigrationConfigBuilder {
430 config: MigrationConfig::default(),
431 }
432 }
433}
434
435#[derive(Debug, Clone)]
449pub struct MigrationConfigBuilder {
450 config: MigrationConfig,
451}
452
453impl MigrationConfigBuilder {
454 pub fn working_directory(mut self, dir: impl Into<PathBuf>) -> Self {
456 self.config.working_directory = dir.into();
457 self
458 }
459
460 pub fn backup_directory(mut self, dir: impl Into<PathBuf>) -> Self {
462 self.config.backup_directory = dir.into();
463 self
464 }
465
466 pub fn max_concurrent(mut self, count: usize) -> Self {
468 self.config.max_concurrent_operations = count;
469 self
470 }
471
472 pub fn timeout(mut self, timeout: chrono::Duration) -> Self {
474 self.config.operation_timeout = timeout;
475 self
476 }
477
478 pub fn dry_run(mut self, enabled: bool) -> Self {
480 self.config.dry_run = enabled;
481 self
482 }
483
484 pub fn verbose(mut self, enabled: bool) -> Self {
486 self.config.verbose = enabled;
487 self
488 }
489
490 pub fn build(self) -> MigrationConfig {
492 self.config
493 }
494}
495
496impl MigrationManager {
497 pub fn new(config: MigrationConfig) -> Result<Self, MigrationError> {
499 std::fs::create_dir_all(&config.working_directory)?;
501 std::fs::create_dir_all(&config.backup_directory)?;
502
503 Ok(Self { config })
504 }
505
506 pub async fn analyze_legacy_system<P: AsRef<std::path::Path>>(
508 &self,
509 config_path: P,
510 ) -> Result<LegacySystemAnalysis, MigrationError> {
511 analyzers::analyze_legacy_system(config_path, &self.config).await
512 }
513
514 pub async fn generate_migration_plan(
516 &self,
517 analysis: &LegacySystemAnalysis,
518 strategy: Option<MigrationStrategy>,
519 ) -> Result<MigrationPlan, MigrationError> {
520 planners::generate_migration_plan(analysis, strategy, &self.config).await
521 }
522
523 pub async fn validate_migration_plan(
525 &self,
526 plan: &MigrationPlan,
527 ) -> Result<Vec<String>, MigrationError> {
528 validators::validate_migration_plan(plan, &self.config).await
529 }
530
531 pub async fn execute_migration(
533 &self,
534 plan: &MigrationPlan,
535 ) -> Result<MigrationResult, MigrationError> {
536 executors::execute_migration_plan(plan, &self.config).await
537 }
538
539 pub async fn get_migration_status(
541 &self,
542 plan_id: &str,
543 ) -> Result<Option<MigrationResult>, MigrationError> {
544 let status_file = self
545 .config
546 .working_directory
547 .join(format!("{}_status.json", plan_id));
548
549 if !status_file.exists() {
550 return Ok(None);
551 }
552
553 let content = fs::read_to_string(status_file).await?;
554 let result: MigrationResult = serde_json::from_str(&content)?;
555 Ok(Some(result))
556 }
557
558 pub async fn rollback_migration(
560 &self,
561 plan: &MigrationPlan,
562 ) -> Result<MigrationResult, MigrationError> {
563 executors::rollback_migration(plan, &self.config).await
564 }
565
566 pub async fn list_migration_plans(&self) -> Result<Vec<String>, MigrationError> {
568 let mut plans = Vec::new();
569 let mut entries = fs::read_dir(&self.config.working_directory).await?;
570
571 while let Some(entry) = entries.next_entry().await? {
572 let path = entry.path();
573 if path.extension().is_some_and(|ext| ext == "json")
574 && let Some(file_name) = path.file_stem()
575 && let Some(name) = file_name.to_str()
576 && name.ends_with("_plan")
577 {
578 plans.push(name.trim_end_matches("_plan").to_string());
579 }
580 }
581
582 Ok(plans)
583 }
584
585 pub async fn save_migration_plan(
587 &self,
588 plan: &MigrationPlan,
589 ) -> Result<PathBuf, MigrationError> {
590 let plan_file = self
591 .config
592 .working_directory
593 .join(format!("{}_plan.json", plan.id));
594 let content = serde_json::to_string_pretty(plan)?;
595 fs::write(&plan_file, content).await?;
596 Ok(plan_file)
597 }
598
599 pub async fn load_migration_plan(
601 &self,
602 plan_id: &str,
603 ) -> Result<MigrationPlan, MigrationError> {
604 let plan_file = self
605 .config
606 .working_directory
607 .join(format!("{}_plan.json", plan_id));
608 let content = fs::read_to_string(plan_file).await?;
609 let plan: MigrationPlan = serde_json::from_str(&content)?;
610 Ok(plan)
611 }
612
613 pub async fn generate_migration_report(
615 &self,
616 result: &MigrationResult,
617 ) -> Result<String, MigrationError> {
618 let mut report = String::new();
619
620 report.push_str("# Migration Report\n\n");
621 report.push_str(&format!("**Plan ID**: {}\n", result.plan_id));
622 report.push_str(&format!("**Status**: {:?}\n", result.status));
623 report.push_str(&format!("**Started**: {}\n", result.started_at));
624
625 if let Some(completed) = result.completed_at {
626 report.push_str(&format!("**Completed**: {}\n", completed));
627 let duration = completed - result.started_at;
628 report.push_str(&format!(
629 "**Duration**: {} minutes\n",
630 duration.num_minutes()
631 ));
632 }
633
634 report.push_str("\n## Metrics\n\n");
635 report.push_str(&format!(
636 "- Roles migrated: {}\n",
637 result.metrics.roles_migrated
638 ));
639 report.push_str(&format!(
640 "- Permissions migrated: {}\n",
641 result.metrics.permissions_migrated
642 ));
643 report.push_str(&format!(
644 "- Users migrated: {}\n",
645 result.metrics.users_migrated
646 ));
647 report.push_str(&format!(
648 "- Errors: {}\n",
649 result.metrics.errors_encountered
650 ));
651 report.push_str(&format!(
652 "- Warnings: {}\n",
653 result.metrics.warnings_generated
654 ));
655
656 if !result.errors.is_empty() {
657 report.push_str("\n## Errors\n\n");
658 for error in &result.errors {
659 report.push_str(&format!("- {}\n", error));
660 }
661 }
662
663 if !result.warnings.is_empty() {
664 report.push_str("\n## Warnings\n\n");
665 for warning in &result.warnings {
666 report.push_str(&format!("- {}\n", warning));
667 }
668 }
669
670 Ok(report)
671 }
672}
673
674#[cfg(test)]
675mod tests {
676 use super::*;
677
678 #[tokio::test]
679 async fn test_migration_manager_creation() {
680 let config = MigrationConfig::default();
681 let manager = MigrationManager::new(config);
682 assert!(manager.is_ok());
683 }
684
685 #[test]
686 fn test_legacy_system_type_serialization() {
687 let system_type = LegacySystemType::BasicRbac;
688 let serialized = serde_json::to_string(&system_type).unwrap();
689 let deserialized: LegacySystemType = serde_json::from_str(&serialized).unwrap();
690 assert_eq!(system_type, deserialized);
691 }
692
693 #[test]
694 fn test_migration_strategy_serialization() {
695 let strategy = MigrationStrategy::GradualMigration;
696 let serialized = serde_json::to_string(&strategy).unwrap();
697 let deserialized: MigrationStrategy = serde_json::from_str(&serialized).unwrap();
698 assert_eq!(strategy, deserialized);
699 }
700
701 #[test]
702 fn test_risk_level_ordering() {
703 assert!(RiskLevel::Low < RiskLevel::Medium);
704 assert!(RiskLevel::Medium < RiskLevel::High);
705 assert!(RiskLevel::High < RiskLevel::Critical);
706 }
707
708 #[test]
709 fn test_migration_config_dry_run_preset() {
710 let config = MigrationConfig::dry_run();
711 assert!(config.dry_run);
712 assert!(config.verbose);
713 }
714
715 #[test]
716 fn test_migration_config_conservative_preset() {
717 let config = MigrationConfig::conservative();
718 assert_eq!(config.max_concurrent_operations, 1);
719 assert!(config.verbose);
720 assert!(!config.dry_run);
721 }
722
723 #[test]
724 fn test_migration_config_aggressive_preset() {
725 let config = MigrationConfig::aggressive();
726 assert_eq!(config.max_concurrent_operations, 16);
727 }
728
729 #[test]
730 fn test_migration_config_builder() {
731 let config = MigrationConfig::builder()
732 .working_directory("/tmp/migration")
733 .dry_run(true)
734 .max_concurrent(8)
735 .verbose(true)
736 .build();
737 assert_eq!(config.working_directory, PathBuf::from("/tmp/migration"));
738 assert!(config.dry_run);
739 assert_eq!(config.max_concurrent_operations, 8);
740 assert!(config.verbose);
741 }
742
743 #[tokio::test]
744 async fn test_migration_manager_with_dry_run_preset() {
745 let config = MigrationConfig::dry_run();
746 let manager = MigrationManager::new(config);
747 assert!(manager.is_ok());
748 }
749}