1use crate::loader::merge::{merge_toml_values, merge_toml_values_with_origins};
2use hashbrown::HashMap;
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5use toml::Value as TomlValue;
6
7use super::fingerprint::fingerprint_toml_value;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum ConfigLayerSource {
12 System { file: PathBuf },
14 User { file: PathBuf },
16 Project { file: PathBuf },
18 Workspace { file: PathBuf },
20 Runtime,
22}
23
24impl ConfigLayerSource {
25 pub const fn precedence(&self) -> i16 {
27 match self {
28 Self::System { .. } => 10,
29 Self::User { .. } => 20,
30 Self::Project { .. } => 25,
31 Self::Workspace { .. } => 30,
32 Self::Runtime => 40,
33 }
34 }
35
36 pub fn label(&self) -> String {
37 match self {
38 Self::System { file } => format!("system:{}", file.display()),
39 Self::User { file } => format!("user:{}", file.display()),
40 Self::Project { file } => format!("project:{}", file.display()),
41 Self::Workspace { file } => format!("workspace:{}", file.display()),
42 Self::Runtime => "runtime".to_string(),
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct ConfigLayerMetadata {
49 pub name: String,
50 pub version: String,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "snake_case")]
55pub enum LayerDisabledReason {
56 ParseError,
57 LoadError,
58 UntrustedWorkspace,
59 PolicyDisabled,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct ConfigLayerLoadError {
64 pub message: String,
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub struct ConfigLayerEntry {
70 pub source: ConfigLayerSource,
72 pub metadata: ConfigLayerMetadata,
74 pub config: TomlValue,
76 pub disabled_reason: Option<LayerDisabledReason>,
78 pub error: Option<ConfigLayerLoadError>,
80}
81
82impl ConfigLayerEntry {
83 pub fn new(source: ConfigLayerSource, config: TomlValue) -> Self {
85 let metadata = ConfigLayerMetadata {
86 name: source.label(),
87 version: fingerprint_toml_value(&config),
88 };
89 Self {
90 source,
91 metadata,
92 config,
93 disabled_reason: None,
94 error: None,
95 }
96 }
97
98 pub fn disabled(
100 source: ConfigLayerSource,
101 reason: LayerDisabledReason,
102 message: impl Into<String>,
103 ) -> Self {
104 let message = message.into();
105 let config = TomlValue::Table(toml::Table::new());
106 let metadata = ConfigLayerMetadata {
107 name: source.label(),
108 version: fingerprint_toml_value(&TomlValue::String(format!(
109 "{}:{}",
110 source.label(),
111 message
112 ))),
113 };
114 Self {
115 source,
116 metadata,
117 config,
118 disabled_reason: Some(reason),
119 error: Some(ConfigLayerLoadError { message }),
120 }
121 }
122
123 pub fn is_enabled(&self) -> bool {
124 self.disabled_reason.is_none() && self.error.is_none()
125 }
126}
127
128#[derive(Debug, Clone, Default)]
130pub struct ConfigLayerStack {
131 layers: Vec<ConfigLayerEntry>,
132}
133
134impl ConfigLayerStack {
135 pub fn new(layers: Vec<ConfigLayerEntry>) -> Self {
137 Self { layers }
138 }
139
140 pub fn push(&mut self, layer: ConfigLayerEntry) {
142 self.layers.push(layer);
143 }
144
145 pub fn effective_config(&self) -> TomlValue {
147 self.effective_config_with_origins().0
148 }
149
150 pub fn effective_config_with_origins(
152 &self,
153 ) -> (TomlValue, HashMap<String, ConfigLayerMetadata>) {
154 let mut merged = TomlValue::Table(toml::Table::new());
155 let mut origins = HashMap::new();
156 for layer in self.ordered_enabled_layers() {
157 merge_toml_values_with_origins(
158 &mut merged,
159 &layer.config,
160 &mut origins,
161 &layer.metadata,
162 );
163 }
164 (merged, origins)
165 }
166
167 pub fn first_layer_error(&self) -> Option<(&ConfigLayerEntry, &ConfigLayerLoadError)> {
169 for layer in self.ordered_layers() {
170 if let Some(error) = layer.error.as_ref() {
171 return Some((layer, error));
172 }
173 }
174 None
175 }
176
177 pub fn effective_config_without_origins(&self) -> TomlValue {
179 let mut merged = TomlValue::Table(toml::Table::new());
180 for layer in self.ordered_enabled_layers() {
181 merge_toml_values(&mut merged, &layer.config);
182 }
183 merged
184 }
185
186 fn ordered_layers(&self) -> Vec<&ConfigLayerEntry> {
187 let mut with_index: Vec<(usize, &ConfigLayerEntry)> =
188 self.layers.iter().enumerate().collect();
189 with_index.sort_by(|(left_idx, left), (right_idx, right)| {
190 left.source
191 .precedence()
192 .cmp(&right.source.precedence())
193 .then(left_idx.cmp(right_idx))
194 });
195 with_index.into_iter().map(|(_, layer)| layer).collect()
196 }
197
198 fn ordered_enabled_layers(&self) -> Vec<&ConfigLayerEntry> {
199 self.ordered_layers()
200 .into_iter()
201 .filter(|layer| layer.is_enabled())
202 .collect()
203 }
204
205 pub fn layers(&self) -> &[ConfigLayerEntry] {
207 &self.layers
208 }
209}