319 lines
8.9 KiB
Rust
319 lines
8.9 KiB
Rust
//! Path utilities module for centralized path resolution
|
|
//!
|
|
//! This module provides utilities for resolving paths relative to the project root,
|
|
//! eliminating the need for hardcoded "../.." paths throughout the codebase.
|
|
|
|
#![allow(dead_code)]
|
|
|
|
use std::env;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::OnceLock;
|
|
|
|
/// Global project root path, initialized once at startup
|
|
static PROJECT_ROOT: OnceLock<PathBuf> = OnceLock::new();
|
|
|
|
/// Initialize the project root path
|
|
pub fn init_project_root() -> PathBuf {
|
|
PROJECT_ROOT
|
|
.get_or_init(|| {
|
|
// Try to get root path from environment variable first
|
|
if let Ok(root_path) = env::var("PROJECT_ROOT") {
|
|
return PathBuf::from(root_path);
|
|
}
|
|
|
|
// Try to get root path from config if available
|
|
if let Ok(config_root) = env::var("CONFIG_ROOT") {
|
|
return PathBuf::from(config_root);
|
|
}
|
|
|
|
// Fall back to detecting based on current executable or working directory
|
|
detect_project_root()
|
|
})
|
|
.clone()
|
|
}
|
|
|
|
/// Detect the project root directory
|
|
fn detect_project_root() -> PathBuf {
|
|
// Start with current working directory
|
|
let mut current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
|
|
|
// Look for Cargo.toml in current directory and parent directories
|
|
loop {
|
|
let cargo_toml = current_dir.join("Cargo.toml");
|
|
if cargo_toml.exists() {
|
|
// Check if this is a workspace root (has [workspace] section)
|
|
if let Ok(contents) = std::fs::read_to_string(&cargo_toml) {
|
|
if contents.contains("[workspace]") {
|
|
return current_dir;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move up one directory
|
|
if let Some(parent) = current_dir.parent() {
|
|
current_dir = parent.to_path_buf();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we couldn't find workspace root, use current directory
|
|
env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
|
|
}
|
|
|
|
/// Get the project root path
|
|
pub fn get_project_root() -> &'static PathBuf {
|
|
PROJECT_ROOT.get_or_init(|| detect_project_root())
|
|
}
|
|
|
|
/// Resolve a path relative to the project root
|
|
pub fn resolve_from_root<P: AsRef<Path>>(relative_path: P) -> PathBuf {
|
|
get_project_root().join(relative_path)
|
|
}
|
|
|
|
/// Resolve a path relative to the project root and return as string
|
|
#[allow(dead_code)]
|
|
pub fn resolve_from_root_str<P: AsRef<Path>>(relative_path: P) -> String {
|
|
resolve_from_root(relative_path)
|
|
.to_string_lossy()
|
|
.to_string()
|
|
}
|
|
|
|
/// Get the absolute path for a given relative path from project root
|
|
#[allow(dead_code)]
|
|
pub fn get_absolute_path<P: AsRef<Path>>(relative_path: P) -> Result<PathBuf, std::io::Error> {
|
|
let resolved = resolve_from_root(relative_path);
|
|
resolved.canonicalize()
|
|
}
|
|
|
|
/// Check if a path exists relative to project root
|
|
#[allow(dead_code)]
|
|
pub fn exists_from_root<P: AsRef<Path>>(relative_path: P) -> bool {
|
|
resolve_from_root(relative_path).exists()
|
|
}
|
|
|
|
/// Read a file relative to project root
|
|
#[allow(dead_code)]
|
|
pub fn read_file_from_root<P: AsRef<Path>>(relative_path: P) -> Result<String, std::io::Error> {
|
|
let path = resolve_from_root(relative_path);
|
|
std::fs::read_to_string(path)
|
|
}
|
|
|
|
/// Read a file relative to project root, with fallback to embedded content
|
|
#[allow(dead_code)]
|
|
pub fn read_file_from_root_or_embedded<P: AsRef<Path>>(
|
|
relative_path: P,
|
|
embedded_content: &str,
|
|
) -> String {
|
|
read_file_from_root(relative_path).unwrap_or_else(|_| embedded_content.to_string())
|
|
}
|
|
|
|
/// Path constants for commonly used directories
|
|
pub mod paths {
|
|
use super::*;
|
|
|
|
/// Get the config directory path
|
|
#[allow(dead_code)]
|
|
pub fn config_dir() -> PathBuf {
|
|
resolve_from_root("config")
|
|
}
|
|
|
|
/// Get the content directory path
|
|
#[allow(dead_code)]
|
|
pub fn content_dir() -> PathBuf {
|
|
resolve_from_root("content")
|
|
}
|
|
|
|
/// Get the migrations directory path
|
|
pub fn migrations_dir() -> PathBuf {
|
|
resolve_from_root("migrations")
|
|
}
|
|
|
|
/// Get the public directory path
|
|
#[allow(dead_code)]
|
|
pub fn public_dir() -> PathBuf {
|
|
resolve_from_root("public")
|
|
}
|
|
|
|
/// Get the uploads directory path
|
|
#[allow(dead_code)]
|
|
pub fn uploads_dir() -> PathBuf {
|
|
resolve_from_root("uploads")
|
|
}
|
|
|
|
/// Get the logs directory path
|
|
#[allow(dead_code)]
|
|
pub fn logs_dir() -> PathBuf {
|
|
resolve_from_root("logs")
|
|
}
|
|
|
|
/// Get the certs directory path
|
|
#[allow(dead_code)]
|
|
pub fn certs_dir() -> PathBuf {
|
|
resolve_from_root("certs")
|
|
}
|
|
|
|
/// Get the cache directory path
|
|
#[allow(dead_code)]
|
|
pub fn cache_dir() -> PathBuf {
|
|
resolve_from_root("cache")
|
|
}
|
|
|
|
/// Get the data directory path
|
|
#[allow(dead_code)]
|
|
pub fn data_dir() -> PathBuf {
|
|
resolve_from_root("data")
|
|
}
|
|
|
|
/// Get the backup directory path
|
|
#[allow(dead_code)]
|
|
pub fn backup_dir() -> PathBuf {
|
|
resolve_from_root("backups")
|
|
}
|
|
}
|
|
|
|
/// Configuration file utilities
|
|
pub mod config {
|
|
use super::*;
|
|
|
|
/// Get the path to a configuration file
|
|
#[allow(dead_code)]
|
|
pub fn config_file<P: AsRef<Path>>(filename: P) -> PathBuf {
|
|
resolve_from_root(filename)
|
|
}
|
|
|
|
/// Get the path to the main config file
|
|
#[allow(dead_code)]
|
|
pub fn main_config() -> PathBuf {
|
|
config_file("config.toml")
|
|
}
|
|
|
|
/// Get the path to the development config file
|
|
#[allow(dead_code)]
|
|
pub fn dev_config() -> PathBuf {
|
|
config_file("config.dev.toml")
|
|
}
|
|
|
|
/// Get the path to the production config file
|
|
#[allow(dead_code)]
|
|
pub fn prod_config() -> PathBuf {
|
|
config_file("config.prod.toml")
|
|
}
|
|
|
|
/// Read a config file with fallback to embedded content
|
|
#[allow(dead_code)]
|
|
pub fn read_config_or_embedded(filename: &str, embedded: &str) -> String {
|
|
read_file_from_root_or_embedded(filename, embedded)
|
|
}
|
|
}
|
|
|
|
/// Content file utilities
|
|
pub mod content {
|
|
use super::*;
|
|
|
|
/// Get the path to a content file
|
|
#[allow(dead_code)]
|
|
pub fn content_file<P: AsRef<Path>>(filename: P) -> PathBuf {
|
|
resolve_from_root("content").join(filename)
|
|
}
|
|
|
|
/// Read a content file with fallback to embedded content
|
|
#[allow(dead_code)]
|
|
pub fn read_content_or_embedded(filename: &str, embedded: &str) -> String {
|
|
let path = content_file(filename);
|
|
std::fs::read_to_string(path).unwrap_or_else(|_| embedded.to_string())
|
|
}
|
|
}
|
|
|
|
/// Migration file utilities
|
|
pub mod migrations {
|
|
use super::*;
|
|
|
|
/// Get the path to a migration file
|
|
#[allow(dead_code)]
|
|
pub fn migration_file<P: AsRef<Path>>(filename: P) -> PathBuf {
|
|
resolve_from_root("migrations").join(filename)
|
|
}
|
|
|
|
/// Read a migration file
|
|
#[allow(dead_code)]
|
|
pub fn read_migration_file(filename: &str) -> Result<String, std::io::Error> {
|
|
let path = migration_file(filename);
|
|
std::fs::read_to_string(path)
|
|
}
|
|
}
|
|
|
|
/// Macro for getting paths from project root
|
|
#[macro_export]
|
|
macro_rules! project_file {
|
|
($relative_path:expr) => {
|
|
$crate::utils::read_file_from_root($relative_path)
|
|
};
|
|
}
|
|
|
|
/// Macro for getting absolute paths from project root
|
|
#[macro_export]
|
|
macro_rules! project_path {
|
|
($relative_path:expr) => {
|
|
$crate::utils::resolve_from_root($relative_path)
|
|
};
|
|
}
|
|
|
|
/// Initialize the path utilities system
|
|
pub fn init() {
|
|
init_project_root();
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_project_root_detection() {
|
|
let root = get_project_root();
|
|
assert!(root.is_absolute());
|
|
|
|
// Should find the workspace Cargo.toml
|
|
let cargo_toml = root.join("Cargo.toml");
|
|
assert!(cargo_toml.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_path_resolution() {
|
|
let config_path = resolve_from_root("config.toml");
|
|
assert!(config_path.is_absolute());
|
|
assert!(config_path.to_string_lossy().ends_with("config.toml"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_path_constants() {
|
|
let config_dir = paths::config_dir();
|
|
assert!(config_dir.is_absolute());
|
|
assert!(config_dir.to_string_lossy().ends_with("config"));
|
|
|
|
let content_dir = paths::content_dir();
|
|
assert!(content_dir.is_absolute());
|
|
assert!(content_dir.to_string_lossy().ends_with("content"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_utilities() {
|
|
let main_config = config::main_config();
|
|
assert!(main_config.is_absolute());
|
|
assert!(main_config.to_string_lossy().ends_with("config.toml"));
|
|
|
|
let dev_config = config::dev_config();
|
|
assert!(dev_config.is_absolute());
|
|
assert!(dev_config.to_string_lossy().ends_with("config.dev.toml"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_exists_from_root() {
|
|
// Test with a file that should exist
|
|
assert!(exists_from_root("Cargo.toml"));
|
|
|
|
// Test with a file that shouldn't exist
|
|
assert!(!exists_from_root("non_existent_file.txt"));
|
|
}
|
|
}
|