1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Configuration trait and validation types for mcpr modules.
//!
//! Each mcpr module (store, cloud, tunnel, logging, etc.) implements [`ModuleConfig`]
//! on its TOML config section struct. This lets every module own its own defaults,
//! validation logic, and documentation — while the CLI orchestrates validation by
//! iterating over all registered modules.
//!
//! # Design rationale
//!
//! Without this trait, all config validation lives in one monolithic function in
//! `mcpr-cli/src/config.rs`, and every new module must touch that file. With it,
//! each crate validates itself — the CLI just loops over `&[&dyn ModuleConfig]`.
use fmt;
// ── Severity ───────────────────────────────────────────────────────────
/// How serious a configuration issue is.
///
/// - `Error`: the proxy cannot start with this config (e.g., invalid URL, missing required field).
/// - `Warn`: the proxy can start, but behavior may be surprising (e.g., port 0 binds randomly).
// ── ConfigIssue ────────────────────────────────────────────────────────
/// A single validation issue found in a module's configuration.
///
/// Returned by [`ModuleConfig::validate`]. The CLI collects these from all
/// modules and presents them to the user (in `mcpr validate` or on startup).
// ── ModuleConfig trait ─────────────────────────────────────────────────
/// Trait for module-owned configuration sections.
///
/// Each mcpr module implements this on its file config struct (the struct
/// that maps to a `[section]` in `mcpr.toml`). This gives each module
/// ownership over:
///
/// - **Naming**: what TOML section key it lives under.
/// - **Defaults**: runtime-aware defaults (e.g., platform-specific paths).
/// - **Validation**: checking its own fields without the CLI knowing the rules.
///
/// # Example
///
/// ```rust,ignore
/// use mcpr_core::config::{ModuleConfig, ConfigIssue, Severity};
///
/// #[derive(serde::Deserialize, Default)]
/// pub struct FileStoreConfig {
/// pub path: Option<String>,
/// }
///
/// impl ModuleConfig for FileStoreConfig {
/// fn name(&self) -> &'static str { "store" }
///
/// fn validate(&self) -> Vec<ConfigIssue> {
/// let mut issues = vec![];
/// if let Some(ref p) = self.path {
/// if p.is_empty() {
/// issues.push(ConfigIssue {
/// severity: Severity::Error,
/// module: "store",
/// message: "store.path cannot be empty string".into(),
/// });
/// }
/// }
/// issues
/// }
/// }
/// ```
///
/// # CLI integration
///
/// The CLI collects all `&dyn ModuleConfig` and calls `validate()` on each:
///
/// ```rust,ignore
/// let modules: Vec<&dyn ModuleConfig> = vec![&config.store, &config.cloud, ...];
/// let issues: Vec<ConfigIssue> = modules.iter().flat_map(|m| m.validate()).collect();
/// ```