lib

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

roots.rs (11020B)


      1 use std::path::{Path, PathBuf};
      2 
      3 use crate::{
      4     RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPlatform, RadrootsRuntimeNamespace,
      5     RadrootsRuntimePathsError,
      6 };
      7 
      8 #[derive(Debug, Clone, PartialEq, Eq)]
      9 pub struct RadrootsPaths {
     10     pub config: PathBuf,
     11     pub data: PathBuf,
     12     pub cache: PathBuf,
     13     pub logs: PathBuf,
     14     pub run: PathBuf,
     15     pub secrets: PathBuf,
     16 }
     17 
     18 impl RadrootsPaths {
     19     #[must_use]
     20     pub fn from_base_root(base_root: impl AsRef<Path>) -> Self {
     21         let base_root = base_root.as_ref();
     22         Self {
     23             config: base_root.join("config"),
     24             data: base_root.join("data"),
     25             cache: base_root.join("cache"),
     26             logs: base_root.join("logs"),
     27             run: base_root.join("run"),
     28             secrets: base_root.join("secrets"),
     29         }
     30     }
     31 
     32     #[must_use]
     33     pub fn namespaced(&self, namespace: &RadrootsRuntimeNamespace) -> Self {
     34         let relative = namespace.relative_path();
     35         Self {
     36             config: self.config.join(&relative),
     37             data: self.data.join(&relative),
     38             cache: self.cache.join(&relative),
     39             logs: self.logs.join(&relative),
     40             run: self.run.join(&relative),
     41             secrets: self.secrets.join(relative),
     42         }
     43     }
     44 }
     45 
     46 #[derive(Debug, Clone, Default, PartialEq, Eq)]
     47 pub struct RadrootsPathOverrides {
     48     pub repo_local_root: Option<PathBuf>,
     49     pub mobile_roots: Option<RadrootsPaths>,
     50 }
     51 
     52 impl RadrootsPathOverrides {
     53     #[must_use]
     54     pub fn repo_local(base_root: impl Into<PathBuf>) -> Self {
     55         Self {
     56             repo_local_root: Some(base_root.into()),
     57             mobile_roots: None,
     58         }
     59     }
     60 
     61     #[must_use]
     62     pub fn mobile(roots: RadrootsPaths) -> Self {
     63         Self {
     64             repo_local_root: None,
     65             mobile_roots: Some(roots),
     66         }
     67     }
     68 }
     69 
     70 #[derive(Debug, Clone, PartialEq, Eq)]
     71 pub struct RadrootsPathResolver {
     72     platform: RadrootsPlatform,
     73     host_environment: RadrootsHostEnvironment,
     74 }
     75 
     76 impl RadrootsPathResolver {
     77     #[must_use]
     78     pub fn new(platform: RadrootsPlatform, host_environment: RadrootsHostEnvironment) -> Self {
     79         Self {
     80             platform,
     81             host_environment,
     82         }
     83     }
     84 
     85     #[must_use]
     86     pub fn current() -> Self {
     87         Self::new(
     88             RadrootsPlatform::current(),
     89             RadrootsHostEnvironment::from_current_process(),
     90         )
     91     }
     92 
     93     #[must_use]
     94     pub fn platform(&self) -> RadrootsPlatform {
     95         self.platform
     96     }
     97 
     98     pub fn resolve(
     99         &self,
    100         profile: RadrootsPathProfile,
    101         overrides: &RadrootsPathOverrides,
    102     ) -> Result<RadrootsPaths, RadrootsRuntimePathsError> {
    103         match profile {
    104             RadrootsPathProfile::InteractiveUser => self.resolve_interactive_user(),
    105             RadrootsPathProfile::ServiceHost => self.resolve_service_host(),
    106             RadrootsPathProfile::RepoLocal => overrides
    107                 .repo_local_root
    108                 .as_ref()
    109                 .map(RadrootsPaths::from_base_root)
    110                 .ok_or(RadrootsRuntimePathsError::MissingRepoLocalRoot),
    111             RadrootsPathProfile::MobileNative => match self.platform {
    112                 RadrootsPlatform::Android | RadrootsPlatform::Ios => overrides
    113                     .mobile_roots
    114                     .clone()
    115                     .ok_or(RadrootsRuntimePathsError::MissingMobileRoots),
    116                 _ => Err(RadrootsRuntimePathsError::UnsupportedProfilePlatform {
    117                     profile,
    118                     platform: self.platform,
    119                 }),
    120             },
    121         }
    122     }
    123 
    124     fn resolve_interactive_user(&self) -> Result<RadrootsPaths, RadrootsRuntimePathsError> {
    125         match self.platform {
    126             RadrootsPlatform::Linux | RadrootsPlatform::Macos => self
    127                 .host_environment
    128                 .home_dir
    129                 .as_ref()
    130                 .map(|home| RadrootsPaths::from_base_root(home.join(".radroots")))
    131                 .ok_or(RadrootsRuntimePathsError::MissingHomeDir {
    132                     platform: self.platform,
    133                 }),
    134             RadrootsPlatform::Windows => {
    135                 let appdata = self
    136                     .host_environment
    137                     .appdata_dir
    138                     .as_ref()
    139                     .ok_or(RadrootsRuntimePathsError::MissingWindowsUserDirs)?;
    140                 let localappdata = self
    141                     .host_environment
    142                     .localappdata_dir
    143                     .as_ref()
    144                     .ok_or(RadrootsRuntimePathsError::MissingWindowsUserDirs)?;
    145                 let config_root = appdata.join("Radroots");
    146                 let local_root = localappdata.join("Radroots");
    147                 Ok(RadrootsPaths {
    148                     config: config_root.join("config"),
    149                     data: local_root.join("data"),
    150                     cache: local_root.join("cache"),
    151                     logs: local_root.join("logs"),
    152                     run: local_root.join("run"),
    153                     secrets: config_root.join("secrets"),
    154                 })
    155             }
    156             RadrootsPlatform::Android | RadrootsPlatform::Ios => {
    157                 Err(RadrootsRuntimePathsError::UnsupportedProfilePlatform {
    158                     profile: RadrootsPathProfile::InteractiveUser,
    159                     platform: self.platform,
    160                 })
    161             }
    162         }
    163     }
    164 
    165     fn resolve_service_host(&self) -> Result<RadrootsPaths, RadrootsRuntimePathsError> {
    166         match self.platform {
    167             RadrootsPlatform::Windows => {
    168                 let programdata = self
    169                     .host_environment
    170                     .programdata_dir
    171                     .as_ref()
    172                     .ok_or(RadrootsRuntimePathsError::MissingWindowsProgramDataDir)?;
    173                 let base = programdata.join("Radroots");
    174                 Ok(RadrootsPaths {
    175                     config: base.join("config"),
    176                     data: base.join("data"),
    177                     cache: base.join("cache"),
    178                     logs: base.join("logs"),
    179                     run: base.join("run"),
    180                     secrets: base.join("secrets"),
    181                 })
    182             }
    183             RadrootsPlatform::Linux | RadrootsPlatform::Macos => Ok(RadrootsPaths {
    184                 config: PathBuf::from("/etc/radroots"),
    185                 data: PathBuf::from("/var/lib/radroots"),
    186                 cache: PathBuf::from("/var/cache/radroots"),
    187                 logs: PathBuf::from("/var/log/radroots"),
    188                 run: PathBuf::from("/run/radroots"),
    189                 secrets: PathBuf::from("/etc/radroots/secrets"),
    190             }),
    191             RadrootsPlatform::Android | RadrootsPlatform::Ios => {
    192                 Err(RadrootsRuntimePathsError::UnsupportedProfilePlatform {
    193                     profile: RadrootsPathProfile::ServiceHost,
    194                     platform: self.platform,
    195                 })
    196             }
    197         }
    198     }
    199 }
    200 
    201 #[cfg(test)]
    202 mod tests {
    203     use std::path::PathBuf;
    204 
    205     use super::{RadrootsPathOverrides, RadrootsPathResolver, RadrootsPaths};
    206     use crate::{
    207         RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPlatform, RadrootsRuntimePathsError,
    208     };
    209 
    210     #[test]
    211     fn path_override_helpers_only_populate_their_owned_slot() {
    212         let repo_local = RadrootsPathOverrides::repo_local("/repo/.local/radroots");
    213         assert_eq!(
    214             repo_local.repo_local_root,
    215             Some(PathBuf::from("/repo/.local/radroots"))
    216         );
    217         assert!(repo_local.mobile_roots.is_none());
    218 
    219         let mobile_roots = RadrootsPaths::from_base_root("/sandbox");
    220         let mobile = RadrootsPathOverrides::mobile(mobile_roots.clone());
    221         assert!(mobile.repo_local_root.is_none());
    222         assert_eq!(mobile.mobile_roots, Some(mobile_roots));
    223     }
    224 
    225     #[test]
    226     fn resolver_current_uses_process_platform_and_environment() {
    227         let resolver = RadrootsPathResolver::current();
    228         assert_eq!(resolver.platform(), RadrootsPlatform::current());
    229         assert_eq!(
    230             resolver,
    231             RadrootsPathResolver::new(
    232                 RadrootsPlatform::current(),
    233                 RadrootsHostEnvironment::from_current_process()
    234             )
    235         );
    236     }
    237 
    238     #[test]
    239     fn mobile_profile_is_rejected_on_non_mobile_platforms() {
    240         let resolver =
    241             RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default());
    242 
    243         let err = resolver
    244             .resolve(
    245                 RadrootsPathProfile::MobileNative,
    246                 &RadrootsPathOverrides::default(),
    247             )
    248             .expect_err("mobile profile should be rejected on linux");
    249 
    250         assert_eq!(
    251             err,
    252             RadrootsRuntimePathsError::UnsupportedProfilePlatform {
    253                 profile: RadrootsPathProfile::MobileNative,
    254                 platform: RadrootsPlatform::Linux,
    255             }
    256         );
    257     }
    258 
    259     #[test]
    260     fn interactive_user_is_rejected_on_mobile_platforms() {
    261         for platform in [RadrootsPlatform::Android, RadrootsPlatform::Ios] {
    262             let resolver = RadrootsPathResolver::new(platform, RadrootsHostEnvironment::default());
    263             let err = resolver
    264                 .resolve(
    265                     RadrootsPathProfile::InteractiveUser,
    266                     &RadrootsPathOverrides::default(),
    267                 )
    268                 .expect_err("interactive_user should be unsupported on mobile");
    269             assert_eq!(
    270                 err,
    271                 RadrootsRuntimePathsError::UnsupportedProfilePlatform {
    272                     profile: RadrootsPathProfile::InteractiveUser,
    273                     platform,
    274                 }
    275             );
    276         }
    277     }
    278 
    279     #[test]
    280     fn service_host_windows_requires_programdata() {
    281         let resolver = RadrootsPathResolver::new(
    282             RadrootsPlatform::Windows,
    283             RadrootsHostEnvironment::default(),
    284         );
    285 
    286         let err = resolver
    287             .resolve(
    288                 RadrootsPathProfile::ServiceHost,
    289                 &RadrootsPathOverrides::default(),
    290             )
    291             .expect_err("service_host on windows should require programdata");
    292 
    293         assert_eq!(err, RadrootsRuntimePathsError::MissingWindowsProgramDataDir);
    294     }
    295 
    296     #[test]
    297     fn service_host_is_rejected_on_mobile_platforms() {
    298         for platform in [RadrootsPlatform::Android, RadrootsPlatform::Ios] {
    299             let resolver = RadrootsPathResolver::new(platform, RadrootsHostEnvironment::default());
    300             let err = resolver
    301                 .resolve(
    302                     RadrootsPathProfile::ServiceHost,
    303                     &RadrootsPathOverrides::default(),
    304                 )
    305                 .expect_err("service_host should be unsupported on mobile");
    306             assert_eq!(
    307                 err,
    308                 RadrootsRuntimePathsError::UnsupportedProfilePlatform {
    309                     profile: RadrootsPathProfile::ServiceHost,
    310                     platform,
    311                 }
    312             );
    313         }
    314     }
    315 }