lib.rs (11945B)
1 #![forbid(unsafe_code)] 2 3 pub mod conventions; 4 pub mod error; 5 pub mod migration; 6 pub mod namespace; 7 pub mod platform; 8 pub mod roots; 9 pub mod service; 10 11 pub use conventions::{ 12 DEFAULT_CONFIG_FILE_NAME, DEFAULT_SERVICE_IDENTITY_FILE_NAME, 13 DEFAULT_SHARED_IDENTITY_FILE_NAME, DEFAULT_SHARED_LOCAL_EVENTS_DB_FILE_NAME, 14 DEFAULT_SHARED_LOCAL_EVENTS_NAMESPACE, DEFAULT_SHARED_LOCAL_EVENTS_NAMESPACE_KIND, 15 DEFAULT_SHARED_LOCAL_EVENTS_NAMESPACE_VALUE, RadrootsBootstrapPaths, 16 default_namespaced_bootstrap_paths, default_shared_identity_path, 17 default_shared_local_events_database_path_from_data_root, 18 default_shared_local_events_database_path_from_shared_accounts_data_root, 19 default_shared_local_events_root_from_data_root, 20 default_shared_local_events_root_from_shared_accounts_data_root, 21 default_shared_runtime_logs_dir, 22 }; 23 pub use error::RadrootsRuntimePathsError; 24 pub use migration::{ 25 RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, RADROOTS_MIGRATION_POSTURE, 26 RadrootsLegacyPathCandidate, RadrootsLegacyPathDetection, RadrootsMigrationReport, 27 inspect_legacy_paths, 28 }; 29 pub use namespace::{RadrootsRuntimeNamespace, RadrootsRuntimeNamespaceKind}; 30 pub use platform::{RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPlatform}; 31 pub use roots::{RadrootsPathOverrides, RadrootsPathResolver, RadrootsPaths}; 32 pub use service::{ 33 RadrootsRuntimeLegacyPathContract, RadrootsRuntimeMigrationContract, 34 RadrootsRuntimePathConfigEntry, RadrootsRuntimePathPolicyContract, 35 RadrootsRuntimePathSelection, RadrootsRuntimePathSelectionError, 36 RadrootsRuntimeSelectionContract, RadrootsRuntimeSelectionOverrideContract, 37 runtime_migration_contract, 38 }; 39 40 #[cfg(test)] 41 mod tests { 42 use std::path::PathBuf; 43 44 use super::{ 45 RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, 46 RadrootsPaths, RadrootsPlatform, RadrootsRuntimeNamespace, RadrootsRuntimePathsError, 47 }; 48 49 #[test] 50 fn interactive_user_linux_uses_home_dotradroots_root() { 51 let resolver = RadrootsPathResolver::new( 52 RadrootsPlatform::Linux, 53 RadrootsHostEnvironment { 54 home_dir: Some(PathBuf::from("/home/treesap")), 55 ..RadrootsHostEnvironment::default() 56 }, 57 ); 58 59 let roots = resolver 60 .resolve( 61 RadrootsPathProfile::InteractiveUser, 62 &RadrootsPathOverrides::default(), 63 ) 64 .expect("resolve linux interactive roots"); 65 66 assert_eq!( 67 roots, 68 RadrootsPaths::from_base_root("/home/treesap/.radroots") 69 ); 70 } 71 72 #[test] 73 fn interactive_user_macos_uses_home_dotradroots_root() { 74 let resolver = RadrootsPathResolver::new( 75 RadrootsPlatform::Macos, 76 RadrootsHostEnvironment { 77 home_dir: Some(PathBuf::from("/Users/treesap")), 78 ..RadrootsHostEnvironment::default() 79 }, 80 ); 81 82 let roots = resolver 83 .resolve( 84 RadrootsPathProfile::InteractiveUser, 85 &RadrootsPathOverrides::default(), 86 ) 87 .expect("resolve macos interactive roots"); 88 89 assert_eq!( 90 roots, 91 RadrootsPaths::from_base_root("/Users/treesap/.radroots") 92 ); 93 } 94 95 #[test] 96 fn interactive_user_windows_uses_native_user_roots() { 97 let resolver = RadrootsPathResolver::new( 98 RadrootsPlatform::Windows, 99 RadrootsHostEnvironment { 100 appdata_dir: Some(PathBuf::from(r"C:\Users\treesap\AppData\Roaming")), 101 localappdata_dir: Some(PathBuf::from(r"C:\Users\treesap\AppData\Local")), 102 ..RadrootsHostEnvironment::default() 103 }, 104 ); 105 106 let roots = resolver 107 .resolve( 108 RadrootsPathProfile::InteractiveUser, 109 &RadrootsPathOverrides::default(), 110 ) 111 .expect("resolve windows interactive roots"); 112 113 assert_eq!( 114 roots, 115 RadrootsPaths { 116 config: PathBuf::from(r"C:\Users\treesap\AppData\Roaming") 117 .join("Radroots") 118 .join("config"), 119 data: PathBuf::from(r"C:\Users\treesap\AppData\Local") 120 .join("Radroots") 121 .join("data"), 122 cache: PathBuf::from(r"C:\Users\treesap\AppData\Local") 123 .join("Radroots") 124 .join("cache"), 125 logs: PathBuf::from(r"C:\Users\treesap\AppData\Local") 126 .join("Radroots") 127 .join("logs"), 128 run: PathBuf::from(r"C:\Users\treesap\AppData\Local") 129 .join("Radroots") 130 .join("run"), 131 secrets: PathBuf::from(r"C:\Users\treesap\AppData\Roaming") 132 .join("Radroots") 133 .join("secrets"), 134 } 135 ); 136 } 137 138 #[test] 139 fn service_host_unix_uses_canonical_service_roots() { 140 let resolver = 141 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 142 143 let roots = resolver 144 .resolve( 145 RadrootsPathProfile::ServiceHost, 146 &RadrootsPathOverrides::default(), 147 ) 148 .expect("resolve service_host roots"); 149 150 assert_eq!( 151 roots, 152 RadrootsPaths { 153 config: PathBuf::from("/etc/radroots"), 154 data: PathBuf::from("/var/lib/radroots"), 155 cache: PathBuf::from("/var/cache/radroots"), 156 logs: PathBuf::from("/var/log/radroots"), 157 run: PathBuf::from("/run/radroots"), 158 secrets: PathBuf::from("/etc/radroots/secrets"), 159 } 160 ); 161 } 162 163 #[test] 164 fn service_host_windows_uses_programdata_roots() { 165 let resolver = RadrootsPathResolver::new( 166 RadrootsPlatform::Windows, 167 RadrootsHostEnvironment { 168 programdata_dir: Some(PathBuf::from(r"C:\ProgramData")), 169 ..RadrootsHostEnvironment::default() 170 }, 171 ); 172 173 let roots = resolver 174 .resolve( 175 RadrootsPathProfile::ServiceHost, 176 &RadrootsPathOverrides::default(), 177 ) 178 .expect("resolve service_host roots"); 179 180 assert_eq!( 181 roots, 182 RadrootsPaths { 183 config: PathBuf::from(r"C:\ProgramData") 184 .join("Radroots") 185 .join("config"), 186 data: PathBuf::from(r"C:\ProgramData") 187 .join("Radroots") 188 .join("data"), 189 cache: PathBuf::from(r"C:\ProgramData") 190 .join("Radroots") 191 .join("cache"), 192 logs: PathBuf::from(r"C:\ProgramData") 193 .join("Radroots") 194 .join("logs"), 195 run: PathBuf::from(r"C:\ProgramData") 196 .join("Radroots") 197 .join("run"), 198 secrets: PathBuf::from(r"C:\ProgramData") 199 .join("Radroots") 200 .join("secrets"), 201 } 202 ); 203 } 204 205 #[test] 206 fn repo_local_requires_explicit_base_root() { 207 let resolver = 208 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 209 210 let err = resolver 211 .resolve( 212 RadrootsPathProfile::RepoLocal, 213 &RadrootsPathOverrides::default(), 214 ) 215 .expect_err("repo_local should require an explicit base root"); 216 217 assert_eq!(err, RadrootsRuntimePathsError::MissingRepoLocalRoot); 218 } 219 220 #[test] 221 fn repo_local_uses_explicit_base_root() { 222 let resolver = 223 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 224 225 let roots = resolver 226 .resolve( 227 RadrootsPathProfile::RepoLocal, 228 &RadrootsPathOverrides::repo_local("/repo/.local/radroots"), 229 ) 230 .expect("resolve repo_local roots"); 231 232 assert_eq!( 233 roots, 234 RadrootsPaths::from_base_root("/repo/.local/radroots") 235 ); 236 } 237 238 #[test] 239 fn mobile_native_requires_explicit_roots() { 240 let resolver = RadrootsPathResolver::new( 241 RadrootsPlatform::Android, 242 RadrootsHostEnvironment::default(), 243 ); 244 245 let err = resolver 246 .resolve( 247 RadrootsPathProfile::MobileNative, 248 &RadrootsPathOverrides::default(), 249 ) 250 .expect_err("mobile_native should require explicit roots"); 251 252 assert_eq!(err, RadrootsRuntimePathsError::MissingMobileRoots); 253 } 254 255 #[test] 256 fn mobile_native_returns_explicit_roots() { 257 let resolver = 258 RadrootsPathResolver::new(RadrootsPlatform::Ios, RadrootsHostEnvironment::default()); 259 let mobile_roots = RadrootsPaths { 260 config: PathBuf::from("/sandbox/config"), 261 data: PathBuf::from("/sandbox/data"), 262 cache: PathBuf::from("/sandbox/cache"), 263 logs: PathBuf::from("/sandbox/logs"), 264 run: PathBuf::from("/sandbox/run"), 265 secrets: PathBuf::from("/sandbox/secrets"), 266 }; 267 268 let roots = resolver 269 .resolve( 270 RadrootsPathProfile::MobileNative, 271 &RadrootsPathOverrides::mobile(mobile_roots.clone()), 272 ) 273 .expect("resolve mobile_native roots"); 274 275 assert_eq!(roots, mobile_roots); 276 } 277 278 #[test] 279 fn namespace_derivation_keeps_runtime_segments_explicit() { 280 let namespace = RadrootsRuntimeNamespace::service("myc").expect("namespace"); 281 let roots = RadrootsPaths::from_base_root("/home/treesap/.radroots"); 282 let namespaced = roots.namespaced(&namespace); 283 284 assert_eq!( 285 namespaced.config, 286 PathBuf::from("/home/treesap/.radroots/config/services/myc") 287 ); 288 assert_eq!( 289 namespaced.data, 290 PathBuf::from("/home/treesap/.radroots/data/services/myc") 291 ); 292 assert_eq!( 293 namespaced.secrets, 294 PathBuf::from("/home/treesap/.radroots/secrets/services/myc") 295 ); 296 } 297 298 #[test] 299 fn namespace_validation_rejects_path_escape_values() { 300 let err = RadrootsRuntimeNamespace::app("../cli").expect_err("invalid namespace"); 301 assert_eq!( 302 err, 303 RadrootsRuntimePathsError::InvalidNamespaceComponent { 304 value: "../cli".to_owned(), 305 } 306 ); 307 } 308 309 #[test] 310 fn interactive_user_unix_requires_home_dir() { 311 let resolver = 312 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 313 314 let err = resolver 315 .resolve( 316 RadrootsPathProfile::InteractiveUser, 317 &RadrootsPathOverrides::default(), 318 ) 319 .expect_err("interactive_user on linux should require a home dir"); 320 321 assert_eq!( 322 err, 323 RadrootsRuntimePathsError::MissingHomeDir { 324 platform: RadrootsPlatform::Linux, 325 } 326 ); 327 } 328 329 #[test] 330 fn interactive_user_windows_requires_native_dirs() { 331 let resolver = RadrootsPathResolver::new( 332 RadrootsPlatform::Windows, 333 RadrootsHostEnvironment::default(), 334 ); 335 336 let err = resolver 337 .resolve( 338 RadrootsPathProfile::InteractiveUser, 339 &RadrootsPathOverrides::default(), 340 ) 341 .expect_err("interactive_user on windows should require native dirs"); 342 343 assert_eq!(err, RadrootsRuntimePathsError::MissingWindowsUserDirs); 344 } 345 }