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