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>> {
42 let path = level.path()?;
43
44 if !path.exists() {
45 debug!(
46 "No config found at {} level: {}",
47 level.display_name(),
48 path.display()
49 );
50 return Ok(None);
51 }
52
53 let contents = fs::read_to_string(&path).await.map_err(|e| {
54 Error::config(format!(
55 "Failed to read {} config: {}",
56 level.display_name(),
57 e
58 ))
59 })?;
60
61 let partial: PartialConfig = toml::from_str(&contents).map_err(|e| {
62 Error::config(format!(
63 "Failed to parse {} config: {}",
64 level.display_name(),
65 e
66 ))
67 })?;
68
69 tracing::info!(
70 "Loaded {} configuration from {}",
71 level.display_name(),
72 path.display()
73 );
74 Ok(Some(partial))
75 }
76
77 pub fn merge(mut self, other: PartialConfig) -> Self {
79 if other.initialized.is_some() {
80 self.initialized = other.initialized;
81 }
82 if other.version.is_some() {
83 self.version = other.version;
84 }
85 if other.update_channel.is_some() {
86 self.update_channel = other.update_channel;
87 }
88 if other.auto_update.is_some() {
89 self.auto_update = other.auto_update;
90 }
91 if other.clippy_rules.is_some() {
92 self.clippy_rules = other.clippy_rules;
93 }
94 if other.max_file_lines.is_some() {
95 self.max_file_lines = other.max_file_lines;
96 }
97 if other.max_function_lines.is_some() {
98 self.max_function_lines = other.max_function_lines;
99 }
100 if other.required_edition.is_some() {
101 self.required_edition = other.required_edition;
102 }
103 if other.required_rust_version.is_some() {
104 self.required_rust_version = other.required_rust_version;
105 }
106 if other.ban_underscore_bandaid.is_some() {
107 self.ban_underscore_bandaid = other.ban_underscore_bandaid;
108 }
109 if other.require_documentation.is_some() {
110 self.require_documentation = other.require_documentation;
111 }
112 if other.custom_rules.is_some() {
113 self.custom_rules = other.custom_rules;
114 }
115 self
116 }
117
118 pub fn to_full_config(self) -> Config {
120 let default = Config::default();
121 Config {
122 initialized: self.initialized.unwrap_or(default.initialized),
123 version: self.version.unwrap_or(default.version),
124 update_channel: self.update_channel.unwrap_or(default.update_channel),
125 auto_update: self.auto_update.unwrap_or(default.auto_update),
126 clippy_rules: self.clippy_rules.unwrap_or(default.clippy_rules),
127 max_file_lines: self.max_file_lines.unwrap_or(default.max_file_lines),
128 max_function_lines: self
129 .max_function_lines
130 .unwrap_or(default.max_function_lines),
131 required_edition: self.required_edition.unwrap_or(default.required_edition),
132 required_rust_version: self
133 .required_rust_version
134 .unwrap_or(default.required_rust_version),
135 ban_underscore_bandaid: self
136 .ban_underscore_bandaid
137 .unwrap_or(default.ban_underscore_bandaid),
138 require_documentation: self
139 .require_documentation
140 .unwrap_or(default.require_documentation),
141 custom_rules: self.custom_rules.unwrap_or(default.custom_rules),
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_partial_config_merge() {
152 let base = PartialConfig {
153 max_file_lines: Some(300),
154 max_function_lines: Some(50),
155 ..Default::default()
156 };
157
158 let override_config = PartialConfig {
159 max_file_lines: Some(400),
160 required_edition: Some("2021".to_string()),
161 ..Default::default()
162 };
163
164 let merged = base.merge(override_config);
165 assert_eq!(merged.max_file_lines, Some(400));
166 assert_eq!(merged.max_function_lines, Some(50));
167 assert_eq!(merged.required_edition, Some("2021".to_string()));
168 }
169
170 #[test]
171 fn test_partial_to_full_config() {
172 let partial = PartialConfig {
173 max_file_lines: Some(500),
174 ..Default::default()
175 };
176
177 let full = partial.to_full_config();
178 assert_eq!(full.max_file_lines, 500);
179 assert_eq!(full.max_function_lines, 50); }
181}