use crate::core::config_tier::ConfigTier;
use crate::core::pattern::FilePattern;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathStatus {
Directory,
File,
NotFound,
}
impl PathStatus {
#[must_use]
pub const fn exists(self) -> bool {
matches!(self, Self::Directory | Self::File)
}
#[must_use]
pub const fn is_dir(self) -> bool {
matches!(self, Self::Directory)
}
#[must_use]
pub const fn is_file(self) -> bool {
matches!(self, Self::File)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceType {
MainFile,
ConfigDir,
FragmentsDir,
Legacy,
}
#[derive(Debug, Clone)]
pub struct ConfigCandidate {
pub path: PathBuf,
pub status: PathStatus,
pub tier: ConfigTier,
pub source_type: SourceType,
}
impl ConfigCandidate {
#[must_use]
pub const fn new(
path: PathBuf,
status: PathStatus,
tier: ConfigTier,
source_type: SourceType,
) -> Self {
Self {
path,
status,
tier,
source_type,
}
}
#[must_use]
pub const fn exists(&self) -> bool {
self.status.exists()
}
#[must_use]
pub const fn is_file(&self) -> bool {
self.status.is_file()
}
#[must_use]
pub const fn is_dir(&self) -> bool {
self.status.is_dir()
}
}
#[derive(Debug, Clone)]
pub struct ConfigDiscovery {
pub preferred_path: PathBuf,
pub found_path: Option<PathBuf>,
pub candidates: Vec<ConfigCandidate>,
pub fragments: Vec<PathBuf>,
}
impl ConfigDiscovery {
#[must_use]
pub const fn new(
preferred_path: PathBuf,
found_path: Option<PathBuf>,
candidates: Vec<ConfigCandidate>,
fragments: Vec<PathBuf>,
) -> Self {
Self {
preferred_path,
found_path,
candidates,
fragments,
}
}
#[must_use]
pub const fn has_config(&self) -> bool {
self.found_path.is_some()
}
#[must_use]
pub fn found_files(&self) -> Vec<&ConfigCandidate> {
self.candidates.iter().filter(|c| c.is_file()).collect()
}
#[must_use]
pub fn found_dirs(&self) -> Vec<&ConfigCandidate> {
self.candidates.iter().filter(|c| c.is_dir()).collect()
}
#[must_use]
pub fn by_tier(&self, tier: ConfigTier) -> Vec<&ConfigCandidate> {
self.candidates.iter().filter(|c| c.tier == tier).collect()
}
#[must_use]
pub fn by_source_type(&self, source_type: SourceType) -> Vec<&ConfigCandidate> {
self.candidates
.iter()
.filter(|c| c.source_type == source_type)
.collect()
}
#[must_use]
pub const fn empty() -> Self {
Self {
preferred_path: PathBuf::new(),
found_path: None,
candidates: Vec::new(),
fragments: Vec::new(),
}
}
}
impl Default for ConfigDiscovery {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug, Clone)]
pub struct DiscoveryOptions {
pub pattern: FilePattern,
pub include_fragments: bool,
pub fragment_dir: String,
pub include_legacy: bool,
}
impl DiscoveryOptions {
#[must_use]
pub fn new() -> Self {
Self {
pattern: FilePattern::default(),
include_fragments: true,
fragment_dir: String::from("conf.d"),
include_legacy: true,
}
}
#[must_use]
pub fn with_pattern(mut self, pattern: FilePattern) -> Self {
self.pattern = pattern;
self
}
#[must_use]
pub const fn with_fragments(mut self, include: bool) -> Self {
self.include_fragments = include;
self
}
#[must_use]
pub fn with_fragment_dir(mut self, dir: impl Into<String>) -> Self {
self.fragment_dir = dir.into();
self
}
#[must_use]
pub const fn with_legacy(mut self, include: bool) -> Self {
self.include_legacy = include;
self
}
}
impl Default for DiscoveryOptions {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_status() {
assert!(PathStatus::Directory.exists());
assert!(PathStatus::File.exists());
assert!(!PathStatus::NotFound.exists());
assert!(PathStatus::Directory.is_dir());
assert!(PathStatus::File.is_file());
}
#[test]
fn test_config_candidate() {
let candidate = ConfigCandidate::new(
PathBuf::from("/test/config"),
PathStatus::File,
ConfigTier::User,
SourceType::MainFile,
);
assert!(candidate.exists());
assert!(candidate.is_file());
assert!(!candidate.is_dir());
}
#[test]
fn test_config_discovery_empty() {
let discovery = ConfigDiscovery::empty();
assert!(!discovery.has_config());
assert!(discovery.found_path.is_none());
assert!(discovery.candidates.is_empty());
assert!(discovery.fragments.is_empty());
}
#[test]
fn test_discovery_options_default() {
let options = DiscoveryOptions::new();
assert!(options.include_fragments);
assert!(options.include_legacy);
assert_eq!(options.fragment_dir, "conf.d");
}
#[test]
fn test_discovery_options_builder() {
let options = DiscoveryOptions::new()
.with_pattern(FilePattern::exact("myapp.toml"))
.with_fragments(false)
.with_fragment_dir("fragments")
.with_legacy(false);
assert!(!options.include_fragments);
assert!(!options.include_legacy);
assert_eq!(options.fragment_dir, "fragments");
}
}