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}
29
30impl RepoConfig {
31 #[must_use]
33 pub fn new(model: impl Into<String>, version: impl Into<String>) -> Self {
34 Self {
35 cache: CacheConfig {
36 local: true,
37 model: model.into(),
38 version: version.into(),
39 },
40 }
41 }
42
43 pub fn to_toml(&self) -> Result<String> {
49 toml::to_string(self)
50 .map_err(|e| Error::Other(anyhow::anyhow!("failed to serialize config: {e}")))
51 }
52
53 pub fn from_toml(s: &str) -> Result<Self> {
59 toml::from_str(s)
60 .map_err(|e| Error::Other(anyhow::anyhow!("failed to deserialize config: {e}")))
61 }
62
63 pub fn save(&self, path: &Path) -> Result<()> {
72 std::fs::create_dir_all(path).map_err(|source| Error::Io {
73 path: path.display().to_string(),
74 source,
75 })?;
76 let file = path.join("config.toml");
77 let contents = self.to_toml()?;
78 std::fs::write(&file, contents).map_err(|source| Error::Io {
79 path: file.display().to_string(),
80 source,
81 })
82 }
83
84 pub fn load(path: &Path) -> Result<Self> {
93 let file = path.join("config.toml");
94 let contents = std::fs::read_to_string(&file).map_err(|source| Error::Io {
95 path: file.display().to_string(),
96 source,
97 })?;
98 Self::from_toml(&contents)
99 }
100}
101
102#[must_use]
108pub fn find_repo_config(start: &Path) -> Option<PathBuf> {
109 let mut current = start.to_path_buf();
110 loop {
111 let candidate = current.join(".ripvec");
112 let config_file = candidate.join("config.toml");
113 if config_file.exists() {
114 return RepoConfig::load(&candidate)
115 .ok()
116 .filter(|cfg| cfg.cache.local)
117 .map(|_| candidate);
118 }
119 match current.parent() {
120 Some(parent) => current = parent.to_path_buf(),
121 None => return None,
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use tempfile::TempDir;
130
131 #[test]
132 fn round_trip_toml() {
133 let cfg = RepoConfig::new("BAAI/bge-small-en-v1.5", "3");
134 let toml_str = cfg.to_toml().expect("serialize");
135 let restored = RepoConfig::from_toml(&toml_str).expect("deserialize");
136 assert!(restored.cache.local);
137 assert_eq!(restored.cache.model, "BAAI/bge-small-en-v1.5");
138 assert_eq!(restored.cache.version, "3");
139 }
140
141 #[test]
142 fn save_and_load() {
143 let dir = TempDir::new().expect("tempdir");
144 let ripvec_dir = dir.path().join(".ripvec");
145 let cfg = RepoConfig::new("nomic-ai/modernbert-embed-base", "3");
146 cfg.save(&ripvec_dir).expect("save");
147 assert!(ripvec_dir.join("config.toml").exists());
148 let loaded = RepoConfig::load(&ripvec_dir).expect("load");
149 assert!(loaded.cache.local);
150 assert_eq!(loaded.cache.model, "nomic-ai/modernbert-embed-base");
151 assert_eq!(loaded.cache.version, "3");
152 }
153
154 #[test]
155 fn find_repo_config_in_current_dir() {
156 let dir = TempDir::new().expect("tempdir");
157 let ripvec_dir = dir.path().join(".ripvec");
158 RepoConfig::new("BAAI/bge-small-en-v1.5", "3")
159 .save(&ripvec_dir)
160 .expect("save");
161 let found = find_repo_config(dir.path());
162 assert_eq!(found.as_deref(), Some(ripvec_dir.as_path()));
163 }
164
165 #[test]
166 fn find_repo_config_in_parent_dir() {
167 let dir = TempDir::new().expect("tempdir");
168 let ripvec_dir = dir.path().join(".ripvec");
169 RepoConfig::new("BAAI/bge-small-en-v1.5", "3")
170 .save(&ripvec_dir)
171 .expect("save");
172 let subdir = dir.path().join("src").join("foo");
173 std::fs::create_dir_all(&subdir).expect("mkdir");
174 let found = find_repo_config(&subdir);
175 assert_eq!(found.as_deref(), Some(ripvec_dir.as_path()));
176 }
177
178 #[test]
179 fn find_repo_config_not_found() {
180 let dir = TempDir::new().expect("tempdir");
181 assert!(find_repo_config(dir.path()).is_none());
183 }
184
185 #[test]
186 fn find_repo_config_ignores_disabled() {
187 let dir = TempDir::new().expect("tempdir");
188 let ripvec_dir = dir.path().join(".ripvec");
189 std::fs::create_dir_all(&ripvec_dir).expect("mkdir");
191 let cfg_str =
192 "[cache]\nlocal = false\nmodel = \"BAAI/bge-small-en-v1.5\"\nversion = \"3\"\n";
193 std::fs::write(ripvec_dir.join("config.toml"), cfg_str).expect("write");
194 assert!(find_repo_config(dir.path()).is_none());
195 }
196}