Skip to main content

sqlite_graphrag/
paths.rs

1use crate::errors::AppError;
2use crate::i18n::validacao;
3use directories::ProjectDirs;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug)]
7pub struct AppPaths {
8    pub db: PathBuf,
9    pub models: PathBuf,
10}
11
12impl AppPaths {
13    pub fn resolve(db_override: Option<&str>) -> Result<Self, AppError> {
14        let proj = ProjectDirs::from("", "", "sqlite-graphrag").ok_or_else(|| {
15            AppError::Io(std::io::Error::other("cannot determine home directory"))
16        })?;
17
18        let cache_root = if let Some(override_dir) = std::env::var_os("SQLITE_GRAPHRAG_CACHE_DIR") {
19            PathBuf::from(override_dir)
20        } else {
21            proj.cache_dir().to_path_buf()
22        };
23
24        let db = if let Some(p) = db_override {
25            validate_path(p)?;
26            PathBuf::from(p)
27        } else if let Ok(env_path) = std::env::var("SQLITE_GRAPHRAG_DB_PATH") {
28            validate_path(&env_path)?;
29            PathBuf::from(env_path)
30        } else {
31            std::env::current_dir()
32                .map_err(AppError::Io)?
33                .join("graphrag.sqlite")
34        };
35
36        Ok(Self {
37            db,
38            models: cache_root.join("models"),
39        })
40    }
41
42    pub fn ensure_dirs(&self) -> Result<(), AppError> {
43        for dir in [parent_or_err(&self.db)?, self.models.as_path()] {
44            std::fs::create_dir_all(dir)?;
45        }
46        Ok(())
47    }
48}
49
50fn validate_path(p: &str) -> Result<(), AppError> {
51    if p.contains("..") {
52        return Err(AppError::Validation(validacao::path_traversal(p)));
53    }
54    Ok(())
55}
56
57pub(crate) fn parent_or_err(path: &Path) -> Result<&Path, AppError> {
58    path.parent().ok_or_else(|| {
59        AppError::Validation(format!(
60            "caminho '{}' não possui componente pai válido",
61            path.display()
62        ))
63    })
64}
65
66#[cfg(test)]
67mod testes {
68    use super::*;
69
70    #[test]
71    fn parent_or_err_aceita_path_normal() {
72        let p = PathBuf::from("/home/usuario/db.sqlite");
73        let pai = parent_or_err(&p).expect("parent valido");
74        assert_eq!(pai, Path::new("/home/usuario"));
75    }
76
77    #[test]
78    fn parent_or_err_aceita_path_relativo() {
79        let p = PathBuf::from("subpasta/arquivo.sqlite");
80        let pai = parent_or_err(&p).expect("parent relativo");
81        assert_eq!(pai, Path::new("subpasta"));
82    }
83
84    #[test]
85    fn parent_or_err_rejeita_raiz_unix() {
86        let p = PathBuf::from("/");
87        let resultado = parent_or_err(&p);
88        assert!(matches!(resultado, Err(AppError::Validation(_))));
89    }
90
91    #[test]
92    fn parent_or_err_rejeita_path_vazio() {
93        let p = PathBuf::from("");
94        let resultado = parent_or_err(&p);
95        assert!(matches!(resultado, Err(AppError::Validation(_))));
96    }
97}