// file: crates/common/game-assets-lib/src/resolver.rs // version: 1 /// Resolves logical asset URIs against separate common and game roots. #[derive(Clone, Debug, Eq, PartialEq)] pub struct AssetResolver { common_root: std::path::PathBuf, game_root: std::path::PathBuf, } impl AssetResolver { /// Creates a resolver from physical common and game asset roots. #[must_use] pub fn new(common_root: impl Into, game_root: impl Into) -> Self { return Self { common_root: common_root.into(), game_root: game_root.into() }; } /// Resolves `common://` or `game://` URI into a physical path without touching the filesystem. pub fn resolve(&self, uri: &str) -> Result { let (root, relative) = if let Some(relative) = uri.strip_prefix("common://") { (&self.common_root, relative) } else if let Some(relative) = uri.strip_prefix("game://") { (&self.game_root, relative) } else { return Err(crate::AssetError::UnsupportedScheme); }; if relative.is_empty() { return Err(crate::AssetError::EmptyPath); } let relative_path = std::path::Path::new(relative); if relative_path.is_absolute() || relative.contains('\\') { return Err(crate::AssetError::InvalidPath); } for component in relative_path.components() { if !matches!(component, std::path::Component::Normal(_)) { return Err(crate::AssetError::InvalidPath); } } return Ok(root.join(relative_path)); } } #[cfg(test)] #[path = "../unit_tests/resolver.rs"] mod tests;