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 },
17 Project { file: PathBuf },
19 Workspace { file: PathBuf },
21 Runtime,
23}
24
25impl ConfigLayerSource {
26 const fn precedence(&self) -> i16 {
28 match self {
29 Self::System { .. } => 10,
30 Self::User { .. } => 20,
31 Self::Project { .. } => 25,
32 Self::Workspace { .. } => 30,
33 Self::Runtime => 40,
34 }
35 }
36
37 pub fn label(&self) -> String {
38 match self {
39 Self::System { file } => format!("system:{}", file.display()),
40 Self::User { file } => format!("user:{}", file.display()),
41 Self::Project { file } => format!("project:{}", file.display()),
42 Self::Workspace { file } => format!("workspace:{}", file.display()),
43 Self::Runtime => "runtime".to_string(),
44 }
45 }
46
47 pub(crate) fn with_file(&self, new_file: PathBuf) -> Self {
49 match self {
50 Self::System { .. } => Self::System { file: new_file },
51 Self::User { .. } => Self::User { file: new_file },
52 Self::Project { .. } => Self::Project { file: new_file },
53 Self::Workspace { .. } => Self::Workspace { file: new_file },
54 Self::Runtime => Self::Runtime,
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct ConfigLayerMetadata {
61 pub name: String,
62 pub version: String,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "snake_case")]
67pub enum LayerDisabledReason {
68 ParseError,
69 LoadError,
70 UntrustedWorkspace,
71 PolicyDisabled,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct ConfigLayerLoadError {
76 pub message: String,
77}
78
79#[derive(Debug, Clone, PartialEq)]
81pub struct ConfigLayerEntry {
82 pub source: ConfigLayerSource,
84 pub metadata: ConfigLayerMetadata,
86 pub(crate) config: TomlValue,
88 pub disabled_reason: Option<LayerDisabledReason>,
90 pub error: Option<ConfigLayerLoadError>,
92}
93
94impl ConfigLayerEntry {
95 pub(crate) fn new(source: ConfigLayerSource, config: TomlValue) -> Self {
97 let metadata = ConfigLayerMetadata {
98 name: source.label(),
99 version: fingerprint_toml_value(&config),
100 };
101 Self {
102 source,
103 metadata,
104 config,
105 disabled_reason: None,
106 error: None,
107 }
108 }
109
110 pub(crate) fn disabled(source: ConfigLayerSource, reason: LayerDisabledReason, message: impl Into<String>) -> Self {
112 let message = message.into();
113 let config = TomlValue::Table(toml::Table::new());
114 let metadata = ConfigLayerMetadata {
115 name: source.label(),
116 version: fingerprint_toml_value(&TomlValue::String(format!("{}:{}", source.label(), message))),
117 };
118 Self {
119 source,
120 metadata,
121 config,
122 disabled_reason: Some(reason),
123 error: Some(ConfigLayerLoadError { message }),
124 }
125 }
126
127 pub fn is_enabled(&self) -> bool {
128 self.disabled_reason.is_none() && self.error.is_none()
129 }
130}
131
132#[derive(Debug, Clone, Default)]
134pub struct ConfigLayerStack {
135 layers: Vec<ConfigLayerEntry>,
136}
137
138impl ConfigLayerStack {
139 pub fn new(layers: Vec<ConfigLayerEntry>) -> Self {
141 Self { layers }
142 }
143
144 pub(crate) fn push(&mut self, layer: ConfigLayerEntry) {
146 self.layers.push(layer);
147 }
148
149 pub(crate) fn retain<F: FnMut(&ConfigLayerEntry) -> bool>(&mut self, mut f: F) {
151 self.layers.retain(|layer| f(layer));
152 }
153
154 pub(crate) fn effective_config(&self) -> TomlValue {
156 self.effective_config_with_origins().0
157 }
158
159 pub fn effective_config_with_origins(&self) -> (TomlValue, HashMap<String, ConfigLayerMetadata>) {
161 let mut merged = TomlValue::Table(toml::Table::new());
162 let mut origins = HashMap::new();
163 for layer in self.ordered_enabled_layers() {
164 merge_toml_values_with_origins(&mut merged, &layer.config, &mut origins, &layer.metadata);
165 }
166 (merged, origins)
167 }
168
169 pub(crate) fn first_layer_error(&self) -> Option<(&ConfigLayerEntry, &ConfigLayerLoadError)> {
171 for layer in self.ordered_layers() {
172 if let Some(error) = layer.error.as_ref() {
173 return Some((layer, error));
174 }
175 }
176 None
177 }
178
179 pub fn effective_config_without_origins(&self) -> TomlValue {
181 let mut merged = TomlValue::Table(toml::Table::new());
182 for layer in self.ordered_enabled_layers() {
183 merge_toml_values(&mut merged, &layer.config);
184 }
185 merged
186 }
187
188 fn ordered_layers(&self) -> Vec<&ConfigLayerEntry> {
189 let mut with_index: Vec<(usize, &ConfigLayerEntry)> = self.layers.iter().enumerate().collect();
190 with_index.sort_by(|(left_idx, left), (right_idx, right)| {
191 left.source
192 .precedence()
193 .cmp(&right.source.precedence())
194 .then(left_idx.cmp(right_idx))
195 });
196 with_index.into_iter().map(|(_, layer)| layer).collect()
197 }
198
199 fn ordered_enabled_layers(&self) -> Vec<&ConfigLayerEntry> {
200 self.ordered_layers().into_iter().filter(|layer| layer.is_enabled()).collect()
201 }
202
203 pub fn layers(&self) -> &[ConfigLayerEntry] {
205 &self.layers
206 }
207}