ripvec_core/cache/
config.rs1use std::path::{Path, PathBuf};
7
8use serde::{Deserialize, Serialize};
9
10use crate::{Error, Result};
11
12#[derive(Debug, Serialize, Deserialize)]
14pub struct RepoConfig {
15 pub cache: CacheConfig,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
21pub struct CacheConfig {
22 pub local: bool,
24 pub model: String,
26 pub version: String,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub auto_stash: Option<bool>,
32}
33
34impl RepoConfig {
35 #[must_use]
37 pub fn new(model: impl Into<String>, version: impl Into<String>) -> Self {
38 Self {
39 cache: CacheConfig {
40 local: true,
41 model: model.into(),
42 version: version.into(),
43 auto_stash: None,
44 },
45 }
46 }
47
48 pub fn to_toml(&self) -> Result<String> {
54 toml::to_string(self)
55 .map_err(|e| Error::Other(anyhow::anyhow!("failed to serialize config: {e}")))
56 }
57
58 pub fn from_toml(s: &str) -> Result<Self> {
64 toml::from_str(s)
65 .map_err(|e| Error::Other(anyhow::anyhow!("failed to deserialize config: {e}")))
66 }
67
68 pub fn save(&self, path: &Path) -> Result<()> {
77 std::fs::create_dir_all(path).map_err(|source| Error::Io {
78 path: path.display().to_string(),
79 source,
80 })?;
81 let file = path.join("config.toml");
82 let contents = self.to_toml()?;
83 std::fs::write(&file, contents).map_err(|source| Error::Io {
84 path: file.display().to_string(),
85 source,
86 })
87 }
88
89 pub fn load(path: &Path) -> Result<Self> {
98 let file = path.join("config.toml");
99 let contents = std::fs::read_to_string(&file).map_err(|source| Error::Io {
100 path: file.display().to_string(),
101 source,
102 })?;
103 Self::from_toml(&contents)
104 }
105}
106
107#[must_use]
113pub fn find_repo_config(start: &Path) -> Option<PathBuf> {
114 let mut current = start.to_path_buf();
115 loop {
116 let candidate = current.join(".ripvec");
117 let config_file = candidate.join("config.toml");
118 if config_file.exists() {
119 return RepoConfig::load(&candidate)
120 .ok()
121 .filter(|cfg| cfg.cache.local)
122 .map(|_| candidate);
123 }
124 match current.parent() {
125 Some(parent) => current = parent.to_path_buf(),
126 None => return None,
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use tempfile::TempDir;
135
136 #[test]
137 fn round_trip_toml() {
138 let cfg = RepoConfig::new("BAAI/bge-small-en-v1.5", "3");
139 let toml_str = cfg.to_toml().expect("serialize");
140 let restored = RepoConfig::from_toml(&toml_str).expect("deserialize");
141 assert!(restored.cache.local);
142 assert_eq!(restored.cache.model, "BAAI/bge-small-en-v1.5");
143 assert_eq!(restored.cache.version, "3");
144 }
145
146 #[test]
147 fn save_and_load() {
148 let dir = TempDir::new().expect("tempdir");
149 let ripvec_dir = dir.path().join(".ripvec");
150 let cfg = RepoConfig::new("nomic-ai/modernbert-embed-base", "3");
151 cfg.save(&ripvec_dir).expect("save");
152 assert!(ripvec_dir.join("config.toml").exists());
153 let loaded = RepoConfig::load(&ripvec_dir).expect("load");
154 assert!(loaded.cache.local);
155 assert_eq!(loaded.cache.model, "nomic-ai/modernbert-embed-base");
156 assert_eq!(loaded.cache.version, "3");
157 }
158
159 #[test]
160 fn find_repo_config_in_current_dir() {
161 let dir = TempDir::new().expect("tempdir");
162 let ripvec_dir = dir.path().join(".ripvec");
163 RepoConfig::new("BAAI/bge-small-en-v1.5", "3")
164 .save(&ripvec_dir)
165 .expect("save");
166 let found = find_repo_config(dir.path());
167 assert_eq!(found.as_deref(), Some(ripvec_dir.as_path()));
168 }
169
170 #[test]
171 fn find_repo_config_in_parent_dir() {
172 let dir = TempDir::new().expect("tempdir");
173 let ripvec_dir = dir.path().join(".ripvec");
174 RepoConfig::new("BAAI/bge-small-en-v1.5", "3")
175 .save(&ripvec_dir)
176 .expect("save");
177 let subdir = dir.path().join("src").join("foo");
178 std::fs::create_dir_all(&subdir).expect("mkdir");
179 let found = find_repo_config(&subdir);
180 assert_eq!(found.as_deref(), Some(ripvec_dir.as_path()));
181 }
182
183 #[test]
184 fn find_repo_config_not_found() {
185 let dir = TempDir::new().expect("tempdir");
186 assert!(find_repo_config(dir.path()).is_none());
188 }
189
190 #[test]
191 fn find_repo_config_ignores_disabled() {
192 let dir = TempDir::new().expect("tempdir");
193 let ripvec_dir = dir.path().join(".ripvec");
194 std::fs::create_dir_all(&ripvec_dir).expect("mkdir");
196 let cfg_str =
197 "[cache]\nlocal = false\nmodel = \"BAAI/bge-small-en-v1.5\"\nversion = \"3\"\n";
198 std::fs::write(ripvec_dir.join("config.toml"), cfg_str).expect("write");
199 assert!(find_repo_config(dir.path()).is_none());
200 }
201}