cfgmatic_paths/core/
discovery.rs1use crate::core::config_tier::ConfigTier;
4use crate::core::pattern::FilePattern;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum PathStatus {
10 Directory,
12 File,
14 NotFound,
16}
17
18impl PathStatus {
19 #[must_use]
21 pub const fn exists(self) -> bool {
22 matches!(self, Self::Directory | Self::File)
23 }
24
25 #[must_use]
27 pub const fn is_dir(self) -> bool {
28 matches!(self, Self::Directory)
29 }
30
31 #[must_use]
33 pub const fn is_file(self) -> bool {
34 matches!(self, Self::File)
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum SourceType {
41 MainFile,
43 ConfigDir,
45 FragmentsDir,
47 Legacy,
49}
50
51#[derive(Debug, Clone)]
53pub struct ConfigCandidate {
54 pub path: PathBuf,
56 pub status: PathStatus,
58 pub tier: ConfigTier,
60 pub source_type: SourceType,
62}
63
64impl ConfigCandidate {
65 #[must_use]
67 pub const fn new(
68 path: PathBuf,
69 status: PathStatus,
70 tier: ConfigTier,
71 source_type: SourceType,
72 ) -> Self {
73 Self {
74 path,
75 status,
76 tier,
77 source_type,
78 }
79 }
80
81 #[must_use]
83 pub const fn exists(&self) -> bool {
84 self.status.exists()
85 }
86
87 #[must_use]
89 pub const fn is_file(&self) -> bool {
90 self.status.is_file()
91 }
92
93 #[must_use]
95 pub const fn is_dir(&self) -> bool {
96 self.status.is_dir()
97 }
98}
99
100#[derive(Debug, Clone)]
106pub struct ConfigDiscovery {
107 pub preferred_path: PathBuf,
109 pub found_path: Option<PathBuf>,
111 pub candidates: Vec<ConfigCandidate>,
113 pub fragments: Vec<PathBuf>,
115}
116
117impl ConfigDiscovery {
118 #[must_use]
120 pub const fn new(
121 preferred_path: PathBuf,
122 found_path: Option<PathBuf>,
123 candidates: Vec<ConfigCandidate>,
124 fragments: Vec<PathBuf>,
125 ) -> Self {
126 Self {
127 preferred_path,
128 found_path,
129 candidates,
130 fragments,
131 }
132 }
133
134 #[must_use]
136 pub const fn has_config(&self) -> bool {
137 self.found_path.is_some()
138 }
139
140 #[must_use]
142 pub fn found_files(&self) -> Vec<&ConfigCandidate> {
143 self.candidates.iter().filter(|c| c.is_file()).collect()
144 }
145
146 #[must_use]
148 pub fn found_dirs(&self) -> Vec<&ConfigCandidate> {
149 self.candidates.iter().filter(|c| c.is_dir()).collect()
150 }
151
152 #[must_use]
154 pub fn by_tier(&self, tier: ConfigTier) -> Vec<&ConfigCandidate> {
155 self.candidates.iter().filter(|c| c.tier == tier).collect()
156 }
157
158 #[must_use]
160 pub fn by_source_type(&self, source_type: SourceType) -> Vec<&ConfigCandidate> {
161 self.candidates
162 .iter()
163 .filter(|c| c.source_type == source_type)
164 .collect()
165 }
166
167 #[must_use]
169 pub const fn empty() -> Self {
170 Self {
171 preferred_path: PathBuf::new(),
172 found_path: None,
173 candidates: Vec::new(),
174 fragments: Vec::new(),
175 }
176 }
177}
178
179impl Default for ConfigDiscovery {
180 fn default() -> Self {
181 Self::empty()
182 }
183}
184
185#[derive(Debug, Clone)]
187pub struct DiscoveryOptions {
188 pub pattern: FilePattern,
190 pub include_fragments: bool,
192 pub fragment_dir: String,
194 pub include_legacy: bool,
196}
197
198impl DiscoveryOptions {
199 #[must_use]
201 pub fn new() -> Self {
202 Self {
203 pattern: FilePattern::default(),
204 include_fragments: true,
205 fragment_dir: String::from("conf.d"),
206 include_legacy: true,
207 }
208 }
209
210 #[must_use]
212 pub fn with_pattern(mut self, pattern: FilePattern) -> Self {
213 self.pattern = pattern;
214 self
215 }
216
217 #[must_use]
219 pub const fn with_fragments(mut self, include: bool) -> Self {
220 self.include_fragments = include;
221 self
222 }
223
224 #[must_use]
226 pub fn with_fragment_dir(mut self, dir: impl Into<String>) -> Self {
227 self.fragment_dir = dir.into();
228 self
229 }
230
231 #[must_use]
233 pub const fn with_legacy(mut self, include: bool) -> Self {
234 self.include_legacy = include;
235 self
236 }
237}
238
239impl Default for DiscoveryOptions {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_path_status() {
251 assert!(PathStatus::Directory.exists());
252 assert!(PathStatus::File.exists());
253 assert!(!PathStatus::NotFound.exists());
254 assert!(PathStatus::Directory.is_dir());
255 assert!(PathStatus::File.is_file());
256 }
257
258 #[test]
259 fn test_config_candidate() {
260 let candidate = ConfigCandidate::new(
261 PathBuf::from("/test/config"),
262 PathStatus::File,
263 ConfigTier::User,
264 SourceType::MainFile,
265 );
266 assert!(candidate.exists());
267 assert!(candidate.is_file());
268 assert!(!candidate.is_dir());
269 }
270
271 #[test]
272 fn test_config_discovery_empty() {
273 let discovery = ConfigDiscovery::empty();
274 assert!(!discovery.has_config());
275 assert!(discovery.found_path.is_none());
276 assert!(discovery.candidates.is_empty());
277 assert!(discovery.fragments.is_empty());
278 }
279
280 #[test]
281 fn test_discovery_options_default() {
282 let options = DiscoveryOptions::new();
283 assert!(options.include_fragments);
284 assert!(options.include_legacy);
285 assert_eq!(options.fragment_dir, "conf.d");
286 }
287
288 #[test]
289 fn test_discovery_options_builder() {
290 let options = DiscoveryOptions::new()
291 .with_pattern(FilePattern::exact("myapp.toml"))
292 .with_fragments(false)
293 .with_fragment_dir("fragments")
294 .with_legacy(false);
295
296 assert!(!options.include_fragments);
297 assert!(!options.include_legacy);
298 assert_eq!(options.fragment_dir, "fragments");
299 }
300}