1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::search::filter::{CandidateFilter, VisibilityConfig};
6
7use super::config::{CorpusKind, CorpusSpec, IndexConfig, WalkOptions};
8use super::{IndexError, IndexKind};
9
10const META_FILE: &str = "meta.json";
11const STORE_VERSION: u32 = 1;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct StoreMeta {
16 pub version: u32,
17 pub corpus: CorpusMeta,
18 pub walk: WalkMeta,
19 pub filters: FilterMeta,
20 pub indexes: Vec<IndexKind>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct CorpusMeta {
26 pub root: PathBuf,
27 pub kind: CorpusKind,
28 #[serde(default)]
29 pub include_paths: Vec<PathBuf>,
30 #[serde(default)]
31 pub exclude_paths: Vec<PathBuf>,
32}
33
34#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
36pub struct WalkMeta {
37 pub follow_links: bool,
38 #[serde(default)]
39 pub one_file_system: bool,
40 #[serde(default)]
41 pub max_depth: Option<usize>,
42 #[serde(default)]
43 pub max_filesize: Option<u64>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48pub struct FilterMeta {
49 pub visibility: VisibilityConfig,
50}
51
52impl StoreMeta {
53 #[must_use]
55 pub const fn new(
56 corpus: CorpusMeta,
57 walk: WalkMeta,
58 filters: FilterMeta,
59 indexes: Vec<IndexKind>,
60 ) -> Self {
61 Self {
62 version: STORE_VERSION,
63 corpus,
64 walk,
65 filters,
66 indexes,
67 }
68 }
69
70 #[must_use]
72 pub fn path(dir: &Path) -> PathBuf {
73 dir.join(META_FILE)
74 }
75
76 pub fn read(dir: &Path) -> crate::Result<Self> {
82 let meta_path = Self::path(dir);
83 let raw = std::fs::read_to_string(&meta_path)?;
84 serde_json::from_str(&raw).map_err(|e| {
85 crate::Error::Index(IndexError::InvalidManifest {
86 path: meta_path,
87 source: e,
88 })
89 })
90 }
91
92 pub fn write(&self, dir: &Path) -> crate::Result<()> {
98 let meta_path = Self::path(dir);
99 let json = serde_json::to_vec_pretty(self).map_err(|e| {
100 crate::Error::Index(IndexError::InvalidManifest {
101 path: meta_path.clone(),
102 source: e,
103 })
104 })?;
105 std::fs::write(&meta_path, json)?;
106 Ok(())
107 }
108
109 #[must_use]
111 pub fn index_config(&self) -> IndexConfig<'_> {
112 IndexConfig {
113 corpus: CorpusSpec {
114 root: &self.corpus.root,
115 kind: self.corpus.kind,
116 follow_links: self.walk.follow_links,
117 include_paths: &self.corpus.include_paths,
118 exclude_paths: &self.corpus.exclude_paths,
119 },
120 walk: WalkOptions {
121 follow_links: self.walk.follow_links,
122 one_file_system: self.walk.one_file_system,
123 max_depth: self.walk.max_depth,
124 max_filesize: self.walk.max_filesize,
125 },
126 visibility: self.filters.visibility.clone(),
127 }
128 }
129
130 #[must_use]
132 pub fn matches_search_filter(&self, filter: &CandidateFilter) -> bool {
133 self.walk.follow_links == filter.follow_links()
134 && self.walk.one_file_system == filter.one_file_system()
135 && self.walk.max_depth == filter.max_depth()
136 && self.walk.max_filesize == filter.max_filesize()
137 && self.filters.visibility == *filter.visibility()
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::search::filter::IgnoreConfig;
145 use tempfile::TempDir;
146
147 #[test]
148 fn read_write_roundtrip() {
149 let tmp = TempDir::new().expect("create temp dir");
150
151 let meta = StoreMeta::new(
152 CorpusMeta {
153 root: PathBuf::from("/some/root"),
154 kind: CorpusKind::Directory,
155 include_paths: vec![PathBuf::from("only.rs")],
156 exclude_paths: vec![PathBuf::from(".sift")],
157 },
158 WalkMeta {
159 follow_links: true,
160 one_file_system: true,
161 max_depth: Some(3),
162 max_filesize: Some(1024),
163 },
164 FilterMeta {
165 visibility: VisibilityConfig {
166 ignore: IgnoreConfig::standard(),
167 ..VisibilityConfig::default()
168 },
169 },
170 vec![IndexKind::Trigram],
171 );
172 meta.write(tmp.path()).expect("write meta");
173
174 let loaded = StoreMeta::read(tmp.path()).expect("read meta");
175 assert_eq!(loaded.version, meta.version);
176 assert_eq!(loaded.corpus.root, meta.corpus.root);
177 assert_eq!(loaded.corpus.kind, meta.corpus.kind);
178 assert_eq!(loaded.corpus.include_paths, meta.corpus.include_paths);
179 assert_eq!(loaded.walk, meta.walk);
180 assert_eq!(loaded.filters, meta.filters);
181 assert_eq!(loaded.indexes, meta.indexes);
182 }
183
184 #[test]
185 fn path_returns_meta_json() {
186 let p = StoreMeta::path(Path::new("/foo/bar"));
187 assert_eq!(p, PathBuf::from("/foo/bar/meta.json"));
188 }
189}