grapsus_config/multi_file/directory.rs
1//! Convention-based configuration directory support.
2//!
3//! This module provides `ConfigDirectory` for loading configuration
4//! from a structured directory layout.
5
6use anyhow::Result;
7use std::path::{Path, PathBuf};
8
9use crate::Config;
10
11use super::loader::MultiFileLoader;
12
13/// Configuration directory structure support.
14///
15/// Provides convention-based loading from a standard directory layout:
16///
17/// ```text
18/// config/
19/// ├── grapsus.kdl # Main config
20/// ├── listeners/ # Listener definitions
21/// │ ├── http.kdl
22/// │ └── https.kdl
23/// ├── routes/ # Route definitions
24/// │ ├── api.kdl
25/// │ └── static.kdl
26/// ├── upstreams/ # Upstream definitions
27/// │ ├── backend.kdl
28/// │ └── cache.kdl
29/// ├── agents/ # Agent configurations
30/// │ ├── waf.kdl
31/// │ └── auth.kdl
32/// └── environments/ # Environment overrides
33/// ├── development.kdl
34/// ├── staging.kdl
35/// └── production.kdl
36/// ```
37pub struct ConfigDirectory {
38 root: PathBuf,
39}
40
41impl ConfigDirectory {
42 /// Create a new config directory handler.
43 pub fn new(root: impl AsRef<Path>) -> Self {
44 Self {
45 root: root.as_ref().to_path_buf(),
46 }
47 }
48
49 /// Load configuration using convention-based structure.
50 ///
51 /// Optionally applies environment-specific overrides.
52 pub fn load(&self, environment: Option<&str>) -> Result<Config> {
53 let mut loader = MultiFileLoader::new(&self.root);
54
55 // Load main configuration
56 if self.root.join("grapsus.kdl").exists() {
57 loader = loader.with_include("grapsus.kdl");
58 }
59
60 // Load from subdirectories
61 let subdirs = ["listeners", "routes", "upstreams", "agents"];
62 for subdir in subdirs {
63 let dir = self.root.join(subdir);
64 if dir.exists() {
65 loader = loader.with_include(format!("{}/*.kdl", subdir));
66 }
67 }
68
69 // Load environment-specific overrides
70 if let Some(env) = environment {
71 let env_file = format!("environments/{}.kdl", env);
72 let env_path = self.root.join(&env_file);
73 if env_path.exists() {
74 loader = loader.with_include(env_file);
75 }
76 }
77
78 // Exclude example and backup files
79 loader = loader
80 .with_exclude("*.example.kdl")
81 .with_exclude("*.bak")
82 .with_exclude("*~");
83
84 loader.load()
85 }
86}