qubit_config/
config_prefix_view.rs1#![allow(private_bounds)]
11
12use std::borrow::Cow;
13
14use qubit_value::MultiValues;
15use qubit_value::multi_values::{MultiValuesFirstGetter, MultiValuesGetter};
16
17use crate::config::Config;
18use crate::config_reader::ConfigReader;
19use crate::from::FromConfig;
20use crate::options::ConfigReadOptions;
21use crate::{ConfigName, ConfigResult, Property};
22
23#[derive(Debug, Clone)]
33pub struct ConfigPrefixView<'a> {
34 config: &'a Config,
35 prefix: String,
36 full_prefix: Option<String>,
37}
38
39impl<'a> ConfigPrefixView<'a> {
40 #[inline]
52 pub(crate) fn new(config: &'a Config, prefix: &str) -> Self {
53 let normalized_prefix = prefix.trim_matches('.').to_string();
54 let full_prefix = if normalized_prefix.is_empty() {
55 None
56 } else {
57 Some(format!("{normalized_prefix}."))
58 };
59 Self {
60 config,
61 prefix: normalized_prefix,
62 full_prefix,
63 }
64 }
65
66 #[inline]
72 pub fn prefix(&self) -> &str {
73 &self.prefix
74 }
75
76 pub fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'a> {
87 let child = prefix.trim_matches('.');
88 if self.prefix.is_empty() {
89 ConfigPrefixView::new(self.config, child)
90 } else if child.is_empty() {
91 ConfigPrefixView::new(self.config, self.prefix.as_str())
92 } else {
93 ConfigPrefixView::new(self.config, &format!("{}.{}", self.prefix, child))
94 }
95 }
96
97 fn resolve_key_cow<'b>(&'b self, name: &'b str) -> Cow<'b, str> {
111 if self.prefix.is_empty() || name.is_empty() {
112 return Cow::Borrowed(name);
113 }
114 if name == self.prefix {
115 return Cow::Borrowed(name);
116 }
117 let full_prefix = self
118 .full_prefix
119 .as_deref()
120 .expect("full_prefix must exist for non-empty prefix");
121 if name.starts_with(full_prefix) {
122 return Cow::Borrowed(name);
123 }
124 Cow::Owned(format!("{}.{}", self.prefix, name))
125 }
126
127 fn visible_entries<'b>(&'b self) -> Box<dyn Iterator<Item = (&'b str, &'b Property)> + 'b> {
128 let prefix = self.prefix.as_str();
129 if prefix.is_empty() {
130 return Box::new(self.config.properties.iter().map(|(k, v)| (k.as_str(), v)));
131 }
132 let full_prefix = self
133 .full_prefix
134 .as_deref()
135 .expect("full_prefix must exist for non-empty prefix");
136 Box::new(self.config.properties.iter().filter_map(move |(k, v)| {
137 if k == prefix {
138 Some((prefix, v))
139 } else {
140 k.strip_prefix(full_prefix).map(|stripped| (stripped, v))
141 }
142 }))
143 }
144
145 fn effective_root_prefix(&self, sub_prefix: &str) -> String {
148 let child = sub_prefix.trim_matches('.');
149 if self.prefix.is_empty() {
150 child.to_string()
151 } else if child.is_empty() {
152 self.prefix.clone()
153 } else {
154 format!("{}.{}", self.prefix, child)
155 }
156 }
157}
158
159impl<'a> ConfigReader for ConfigPrefixView<'a> {
160 #[inline]
161 fn is_enable_variable_substitution(&self) -> bool {
162 self.config.is_enable_variable_substitution()
163 }
164
165 #[inline]
166 fn max_substitution_depth(&self) -> usize {
167 self.config.max_substitution_depth()
168 }
169
170 #[inline]
171 fn read_options(&self) -> &ConfigReadOptions {
172 self.config.read_options()
173 }
174
175 #[inline]
176 fn description(&self) -> Option<&str> {
177 self.config.description()
178 }
179
180 fn get_property(&self, name: impl ConfigName) -> Option<&Property> {
181 name.with_config_name(|name| {
182 let key = self.resolve_key_cow(name);
183 self.config.get_property(key.as_ref())
184 })
185 }
186
187 fn len(&self) -> usize {
188 self.visible_entries().count()
189 }
190
191 fn is_empty(&self) -> bool {
192 self.visible_entries().next().is_none()
193 }
194
195 fn keys(&self) -> Vec<String> {
196 self.visible_entries().map(|(k, _)| k.to_string()).collect()
197 }
198
199 fn contains(&self, name: impl ConfigName) -> bool {
200 name.with_config_name(|name| {
201 let key = self.resolve_key_cow(name);
202 self.config.contains(key.as_ref())
203 })
204 }
205
206 fn get_strict<T>(&self, name: impl ConfigName) -> ConfigResult<T>
207 where
208 MultiValues: MultiValuesFirstGetter<T>,
209 {
210 name.with_config_name(|name| {
211 let key = self.resolve_key_cow(name);
212 self.config.get_strict(key.as_ref())
213 })
214 }
215
216 fn get_list<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
217 where
218 T: FromConfig,
219 {
220 name.with_config_name(|name| {
221 let key = self.resolve_key_cow(name);
222 self.config.get_list(key.as_ref())
223 })
224 }
225
226 fn get_list_strict<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
227 where
228 MultiValues: MultiValuesGetter<T>,
229 {
230 name.with_config_name(|name| {
231 let key = self.resolve_key_cow(name);
232 self.config.get_list_strict(key.as_ref())
233 })
234 }
235
236 fn get_optional_list<T>(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<T>>>
237 where
238 T: FromConfig,
239 {
240 name.with_config_name(|name| {
241 let key = self.resolve_key_cow(name);
242 self.config.get_optional_list(key.as_ref())
243 })
244 }
245
246 fn contains_prefix(&self, prefix: &str) -> bool {
247 self.visible_entries().any(|(k, _)| k.starts_with(prefix))
248 }
249
250 fn iter_prefix<'b>(
251 &'b self,
252 prefix: &'b str,
253 ) -> Box<dyn Iterator<Item = (&'b str, &'b Property)> + 'b> {
254 Box::new(
255 self.visible_entries()
256 .filter(move |(k, _)| k.starts_with(prefix)),
257 )
258 }
259
260 fn iter<'b>(&'b self) -> Box<dyn Iterator<Item = (&'b str, &'b Property)> + 'b> {
261 self.visible_entries()
262 }
263
264 fn is_null(&self, name: impl ConfigName) -> bool {
265 name.with_config_name(|name| {
266 let key = self.resolve_key_cow(name);
267 self.config.is_null(key.as_ref())
268 })
269 }
270
271 fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
272 let full = self.effective_root_prefix(prefix);
273 self.config.subconfig(&full, strip_prefix)
274 }
275
276 fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
277 where
278 T: serde::de::DeserializeOwned,
279 {
280 let full = self.effective_root_prefix(prefix);
281 self.config.deserialize(&full)
282 }
283
284 #[inline]
285 fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'a> {
286 ConfigPrefixView::prefix_view(self, prefix)
287 }
288
289 fn resolve_key(&self, name: impl ConfigName) -> String {
290 name.with_config_name(|name| {
291 if name.is_empty() {
292 return self.prefix.clone();
293 }
294 self.resolve_key_cow(name).into_owned()
295 })
296 }
297}