lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

migration.rs (5730B)


      1 use std::path::PathBuf;
      2 
      3 pub const RADROOTS_MIGRATION_POSTURE: &str = "explicit_operator_import_required";
      4 pub const RADROOTS_MIGRATION_COMPATIBILITY_WINDOW: &str = "detect_and_report_only";
      5 
      6 #[derive(Debug, Clone, PartialEq, Eq)]
      7 pub struct RadrootsLegacyPathCandidate {
      8     pub id: String,
      9     pub description: String,
     10     pub path: PathBuf,
     11     pub destination: Option<PathBuf>,
     12     pub import_hint: String,
     13 }
     14 
     15 #[derive(Debug, Clone, PartialEq, Eq)]
     16 pub struct RadrootsLegacyPathDetection {
     17     pub id: String,
     18     pub description: String,
     19     pub path: PathBuf,
     20     pub destination: Option<PathBuf>,
     21     pub import_hint: String,
     22 }
     23 
     24 #[derive(Debug, Clone, PartialEq, Eq)]
     25 pub struct RadrootsMigrationReport {
     26     pub posture: &'static str,
     27     pub state: &'static str,
     28     pub silent_startup_relocation: bool,
     29     pub compatibility_window: &'static str,
     30     pub detected_legacy_paths: Vec<RadrootsLegacyPathDetection>,
     31 }
     32 
     33 impl RadrootsLegacyPathCandidate {
     34     #[must_use]
     35     pub fn new(
     36         id: impl Into<String>,
     37         description: impl Into<String>,
     38         path: impl Into<PathBuf>,
     39         destination: Option<PathBuf>,
     40         import_hint: impl Into<String>,
     41     ) -> Self {
     42         Self {
     43             id: id.into(),
     44             description: description.into(),
     45             path: path.into(),
     46             destination,
     47             import_hint: import_hint.into(),
     48         }
     49     }
     50 
     51     fn into_detection(self) -> RadrootsLegacyPathDetection {
     52         RadrootsLegacyPathDetection {
     53             id: self.id,
     54             description: self.description,
     55             path: self.path,
     56             destination: self.destination,
     57             import_hint: self.import_hint,
     58         }
     59     }
     60 }
     61 
     62 impl RadrootsMigrationReport {
     63     #[must_use]
     64     pub fn empty() -> Self {
     65         Self::from_detected_legacy_paths(Vec::new())
     66     }
     67 
     68     #[must_use]
     69     pub fn from_detected_legacy_paths(
     70         detected_legacy_paths: Vec<RadrootsLegacyPathDetection>,
     71     ) -> Self {
     72         let state = if detected_legacy_paths.is_empty() {
     73             "ready"
     74         } else {
     75             "legacy_state_detected"
     76         };
     77         Self {
     78             posture: RADROOTS_MIGRATION_POSTURE,
     79             state,
     80             silent_startup_relocation: false,
     81             compatibility_window: RADROOTS_MIGRATION_COMPATIBILITY_WINDOW,
     82             detected_legacy_paths,
     83         }
     84     }
     85 }
     86 
     87 #[must_use]
     88 pub fn inspect_legacy_paths(
     89     candidates: impl IntoIterator<Item = RadrootsLegacyPathCandidate>,
     90 ) -> RadrootsMigrationReport {
     91     let detected = candidates
     92         .into_iter()
     93         .filter(|candidate| candidate.path.exists())
     94         .map(RadrootsLegacyPathCandidate::into_detection)
     95         .collect();
     96     RadrootsMigrationReport::from_detected_legacy_paths(detected)
     97 }
     98 
     99 #[cfg(test)]
    100 mod tests {
    101     use std::path::PathBuf;
    102 
    103     use super::{
    104         RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, RADROOTS_MIGRATION_POSTURE,
    105         RadrootsLegacyPathCandidate, RadrootsMigrationReport, inspect_legacy_paths,
    106     };
    107 
    108     fn unique_test_dir() -> PathBuf {
    109         let nanos = std::time::SystemTime::now()
    110             .duration_since(std::time::UNIX_EPOCH)
    111             .expect("clock")
    112             .as_nanos();
    113         let path = std::env::temp_dir().join(format!("radroots_runtime_paths-test-{nanos}"));
    114         std::fs::create_dir_all(&path).expect("create temp test dir");
    115         path
    116     }
    117 
    118     #[test]
    119     fn inspect_legacy_paths_reports_only_paths_that_exist() {
    120         let temp = unique_test_dir();
    121         let existing = temp.join("old-state");
    122         let missing = temp.join("missing-state");
    123         std::fs::write(&existing, "legacy").expect("write legacy marker");
    124 
    125         let report = inspect_legacy_paths([
    126             RadrootsLegacyPathCandidate::new(
    127                 "old-state",
    128                 "old state",
    129                 &existing,
    130                 Some(temp.join("new-state")),
    131                 "run the explicit importer",
    132             ),
    133             RadrootsLegacyPathCandidate::new(
    134                 "missing-state",
    135                 "missing state",
    136                 &missing,
    137                 None,
    138                 "nothing to do",
    139             ),
    140         ]);
    141 
    142         assert_eq!(report.posture, RADROOTS_MIGRATION_POSTURE);
    143         assert_eq!(report.state, "legacy_state_detected");
    144         assert!(!report.silent_startup_relocation);
    145         assert_eq!(
    146             report.compatibility_window,
    147             RADROOTS_MIGRATION_COMPATIBILITY_WINDOW
    148         );
    149         assert_eq!(report.detected_legacy_paths.len(), 1);
    150         assert_eq!(report.detected_legacy_paths[0].id, "old-state");
    151         assert_eq!(report.detected_legacy_paths[0].path, existing);
    152         std::fs::remove_dir_all(temp).expect("remove temp test dir");
    153     }
    154 
    155     #[test]
    156     fn inspect_legacy_paths_is_ready_when_no_candidate_exists() {
    157         let temp = unique_test_dir();
    158 
    159         let report = inspect_legacy_paths([RadrootsLegacyPathCandidate::new(
    160             "missing-state",
    161             "missing state",
    162             temp.join("missing-state"),
    163             None,
    164             "nothing to do",
    165         )]);
    166 
    167         assert_eq!(report.state, "ready");
    168         assert!(report.detected_legacy_paths.is_empty());
    169         std::fs::remove_dir_all(temp).expect("remove temp test dir");
    170     }
    171 
    172     #[test]
    173     fn empty_report_matches_ready_state() {
    174         let report = RadrootsMigrationReport::empty();
    175         assert_eq!(report.posture, RADROOTS_MIGRATION_POSTURE);
    176         assert_eq!(report.state, "ready");
    177         assert!(!report.silent_startup_relocation);
    178         assert_eq!(
    179             report.compatibility_window,
    180             RADROOTS_MIGRATION_COMPATIBILITY_WINDOW
    181         );
    182         assert!(report.detected_legacy_paths.is_empty());
    183     }
    184 }