use std::path::{Path, PathBuf};
use std::sync::OnceLock;
static PATHS: OnceLock<AppPaths> = OnceLock::new();
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppPaths {
root: PathBuf,
}
impl AppPaths {
pub fn new(root: impl Into<PathBuf>) -> Self {
Self { root: root.into() }
}
pub fn root(&self) -> &Path {
&self.root
}
pub fn env(&self) -> PathBuf {
self.root.join(".env")
}
pub fn public(&self) -> PathBuf {
self.root.join("public")
}
pub fn storage(&self) -> PathBuf {
self.root.join("storage")
}
pub fn database(&self) -> PathBuf {
self.root.join("database")
}
pub fn logs(&self) -> PathBuf {
self.storage().join("logs")
}
pub fn sessions(&self) -> PathBuf {
self.storage().join("sessions")
}
pub fn migrations(&self) -> PathBuf {
self.database().join("migrations")
}
pub fn join(&self, relative: impl AsRef<Path>) -> PathBuf {
self.root.join(relative)
}
pub(crate) fn publish(self) {
let _ = PATHS.set(self);
}
}
impl Default for AppPaths {
fn default() -> Self {
Self::new(std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
}
pub fn resolve_app_root(compiled_in_default: &str) -> String {
env_app_root(std::env::var("LARUST_APP_ROOT").ok(), compiled_in_default)
}
fn env_app_root(env_override: Option<String>, compiled_in_default: &str) -> String {
match env_override {
Some(value) if !value.is_empty() => value,
_ => compiled_in_default.to_string(),
}
}
pub fn try_paths() -> Option<&'static AppPaths> {
PATHS.get()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn env_app_root_uses_the_override_when_set() {
assert_eq!(
env_app_root(Some("/opt/myapp".to_string()), "/build/myapp"),
"/opt/myapp"
);
}
#[test]
fn env_app_root_falls_back_to_the_compiled_in_default_when_unset() {
assert_eq!(env_app_root(None, "/build/myapp"), "/build/myapp");
}
#[test]
fn env_app_root_treats_an_empty_override_the_same_as_unset() {
assert_eq!(
env_app_root(Some(String::new()), "/build/myapp"),
"/build/myapp"
);
}
}