ferrous_forge/config/hierarchy/
partial.rs1use super::ConfigLevel;
4use crate::config::{Config, CustomRule};
5use crate::{Error, Result};
6use serde::{Deserialize, Serialize};
7use tokio::fs;
8use tracing::debug;
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12pub struct PartialConfig {
13 pub initialized: Option<bool>,
15 pub version: Option<String>,
17 pub update_channel: Option<String>,
19 pub auto_update: Option<bool>,
21 pub clippy_rules: Option<Vec<String>>,
23 pub max_file_lines: Option<usize>,
25 pub max_function_lines: Option<usize>,
27 pub required_edition: Option<String>,
29 pub required_rust_version: Option<String>,
31 pub ban_underscore_bandaid: Option<bool>,
33 pub require_documentation: Option<bool>,
35 pub custom_rules: Option<Vec<CustomRule>>,
37}
38
39impl PartialConfig {
40 pub async fn load_from_level(level: ConfigLevel) -> Result<Option<Self>> {
46 let path = level.path()?;
47
48 if !path.exists() {
49 debug!(
50 "No config found at {} level: {}",
51 level.display_name(),
52 path.display()
53 );
54 return Ok(None);
55 }
56
57 let contents = fs::read_to_string(&path).await.map_err(|e| {
58 Error::config(format!(
59 "Failed to read {} config: {}",
60 level.display_name(),
61 e
62 ))
63 })?;
64
65 let partial: PartialConfig = toml::from_str(&contents).map_err(|e| {
66 Error::config(format!(
67 "Failed to parse {} config: {}",
68 level.display_name(),
69 e
70 ))
71 })?;
72
73 tracing::info!(
74 "Loaded {} configuration from {}",
75 level.display_name(),
76 path.display()
77 );
78 Ok(Some(partial))
79 }
80
81 pub fn merge(mut self, other: PartialConfig) -> Self {
83 if other.initialized.is_some() {
84 self.initialized = other.initialized;
85 }
86 if other.version.is_some() {
87 self.version = other.version;
88 }
89 if other.update_channel.is_some() {
90 self.update_channel = other.update_channel;
91 }
92 if other.auto_update.is_some() {
93 self.auto_update = other.auto_update;
94 }
95 if other.clippy_rules.is_some() {
96 self.clippy_rules = other.clippy_rules;
97 }
98 if other.max_file_lines.is_some() {
99 self.max_file_lines = other.max_file_lines;
100 }
101 if other.max_function_lines.is_some() {
102 self.max_function_lines = other.max_function_lines;
103 }
104 if other.required_edition.is_some() {
105 self.required_edition = other.required_edition;
106 }
107 if other.required_rust_version.is_some() {
108 self.required_rust_version = other.required_rust_version;
109 }
110 if other.ban_underscore_bandaid.is_some() {
111 self.ban_underscore_bandaid = other.ban_underscore_bandaid;
112 }
113 if other.require_documentation.is_some() {
114 self.require_documentation = other.require_documentation;
115 }
116 if other.custom_rules.is_some() {
117 self.custom_rules = other.custom_rules;
118 }
119 self
120 }
121
122 pub fn to_full_config(self) -> Config {
124 let default = Config::default();
125 Config {
126 initialized: self.initialized.unwrap_or(default.initialized),
127 version: self.version.unwrap_or(default.version),
128 update_channel: self.update_channel.unwrap_or(default.update_channel),
129 auto_update: self.auto_update.unwrap_or(default.auto_update),
130 clippy_rules: self.clippy_rules.unwrap_or(default.clippy_rules),
131 max_file_lines: self.max_file_lines.unwrap_or(default.max_file_lines),
132 max_function_lines: self
133 .max_function_lines
134 .unwrap_or(default.max_function_lines),
135 required_edition: self.required_edition.unwrap_or(default.required_edition),
136 required_rust_version: self
137 .required_rust_version
138 .unwrap_or(default.required_rust_version),
139 ban_underscore_bandaid: self
140 .ban_underscore_bandaid
141 .unwrap_or(default.ban_underscore_bandaid),
142 require_documentation: self
143 .require_documentation
144 .unwrap_or(default.require_documentation),
145 custom_rules: self.custom_rules.unwrap_or(default.custom_rules),
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_partial_config_merge() {
156 let base = PartialConfig {
157 max_file_lines: Some(300),
158 max_function_lines: Some(50),
159 ..Default::default()
160 };
161
162 let override_config = PartialConfig {
163 max_file_lines: Some(400),
164 required_edition: Some("2021".to_string()),
165 ..Default::default()
166 };
167
168 let merged = base.merge(override_config);
169 assert_eq!(merged.max_file_lines, Some(400));
170 assert_eq!(merged.max_function_lines, Some(50));
171 assert_eq!(merged.required_edition, Some("2021".to_string()));
172 }
173
174 #[test]
175 fn test_partial_to_full_config() {
176 let partial = PartialConfig {
177 max_file_lines: Some(500),
178 ..Default::default()
179 };
180
181 let full = partial.to_full_config();
182 assert_eq!(full.max_file_lines, 500);
183 assert_eq!(full.max_function_lines, 50); }
185}