qubit_config/
config_prefix_view.rs1#![allow(private_bounds)]
11
12use std::borrow::Cow;
13
14use qubit_value::{
15 MultiValues,
16 ValueError,
17};
18
19use crate::config::Config;
20use crate::config_reader::ConfigReader;
21use crate::from::FromConfig;
22use crate::options::ConfigReadOptions;
23use crate::{
24 ConfigName,
25 ConfigResult,
26 Property,
27};
28
29#[derive(Debug, Clone)]
39pub struct ConfigPrefixView<'a> {
40 config: &'a Config,
41 prefix: String,
42 full_prefix: Option<String>,
43}
44
45impl<'a> ConfigPrefixView<'a> {
46 #[inline]
58 pub(crate) fn new(config: &'a Config, prefix: &str) -> Self {
59 let normalized_prefix = prefix.trim_matches('.').to_string();
60 let full_prefix = if normalized_prefix.is_empty() {
61 None
62 } else {
63 Some(format!("{normalized_prefix}."))
64 };
65 Self {
66 config,
67 prefix: normalized_prefix,
68 full_prefix,
69 }
70 }
71
72 #[inline]
78 pub fn prefix(&self) -> &str {
79 &self.prefix
80 }
81
82 pub fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'a> {
93 let child = prefix.trim_matches('.');
94 if self.prefix.is_empty() {
95 ConfigPrefixView::new(self.config, child)
96 } else if child.is_empty() {
97 ConfigPrefixView::new(self.config, self.prefix.as_str())
98 } else {
99 ConfigPrefixView::new(self.config, &format!("{}.{}", self.prefix, child))
100 }
101 }
102
103 fn resolve_key_cow<'b>(&'b self, name: &'b str) -> Cow<'b, str> {
117 if self.prefix.is_empty() || name.is_empty() {
118 return Cow::Borrowed(name);
119 }
120 if name == self.prefix {
121 return Cow::Borrowed(name);
122 }
123 let full_prefix = self
124 .full_prefix
125 .as_deref()
126 .expect("full_prefix must exist for non-empty prefix");
127 if name.starts_with(full_prefix) {
128 return Cow::Borrowed(name);
129 }
130 Cow::Owned(format!("{}.{}", self.prefix, name))
131 }
132
133 fn visible_entries<'b>(&'b self) -> Box<dyn Iterator<Item = (&'b str, &'b Property)> + 'b> {
134 let prefix = self.prefix.as_str();
135 if prefix.is_empty() {
136 return Box::new(self.config.properties.iter().map(|(k, v)| (k.as_str(), v)));
137 }
138 let full_prefix = self
139 .full_prefix
140 .as_deref()
141 .expect("full_prefix must exist for non-empty prefix");
142 Box::new(self.config.properties.iter().filter_map(move |(k, v)| {
143 if k == prefix {
144 Some((prefix, v))
145 } else {
146 k.strip_prefix(full_prefix).map(|stripped| (stripped, v))
147 }
148 }))
149 }
150
151 fn effective_root_prefix(&self, sub_prefix: &str) -> String {
154 let child = sub_prefix.trim_matches('.');
155 if self.prefix.is_empty() {
156 child.to_string()
157 } else if child.is_empty() {
158 self.prefix.clone()
159 } else {
160 format!("{}.{}", self.prefix, child)
161 }
162 }
163}
164
165impl<'a> ConfigReader for ConfigPrefixView<'a> {
166 #[inline]
167 fn is_enable_variable_substitution(&self) -> bool {
168 self.config.is_enable_variable_substitution()
169 }
170
171 #[inline]
172 fn max_substitution_depth(&self) -> usize {
173 self.config.max_substitution_depth()
174 }
175
176 #[inline]
177 fn read_options(&self) -> &ConfigReadOptions {
178 self.config.read_options()
179 }
180
181 #[inline]
182 fn description(&self) -> Option<&str> {
183 self.config.description()
184 }
185
186 fn get_property(&self, name: impl ConfigName) -> Option<&Property> {
187 name.with_config_name(|name| {
188 let key = self.resolve_key_cow(name);
189 self.config.get_property(key.as_ref())
190 })
191 }
192
193 fn len(&self) -> usize {
194 self.visible_entries().count()
195 }
196
197 fn is_empty(&self) -> bool {
198 self.visible_entries().next().is_none()
199 }
200
201 fn keys(&self) -> Vec<String> {
202 self.visible_entries().map(|(k, _)| k.to_string()).collect()
203 }
204
205 fn contains(&self, name: impl ConfigName) -> bool {
206 name.with_config_name(|name| {
207 let key = self.resolve_key_cow(name);
208 self.config.contains(key.as_ref())
209 })
210 }
211
212 fn get_strict<T>(&self, name: impl ConfigName) -> ConfigResult<T>
213 where
214 for<'b> T: TryFrom<&'b MultiValues, Error = ValueError>,
215 {
216 name.with_config_name(|name| {
217 let key = self.resolve_key_cow(name);
218 self.config.get_strict(key.as_ref())
219 })
220 }
221
222 fn get_list<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
223 where
224 T: FromConfig,
225 {
226 name.with_config_name(|name| {
227 let key = self.resolve_key_cow(name);
228 self.config.get_list(key.as_ref())
229 })
230 }
231
232 fn get_list_strict<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
233 where
234 for<'b> Vec<T>: TryFrom<&'b MultiValues, Error = ValueError>,
235 {
236 name.with_config_name(|name| {
237 let key = self.resolve_key_cow(name);
238 self.config.get_list_strict(key.as_ref())
239 })
240 }
241
242 fn get_optional_list<T>(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<T>>>
243 where
244 T: FromConfig,
245 {
246 name.with_config_name(|name| {
247 let key = self.resolve_key_cow(name);
248 self.config.get_optional_list(key.as_ref())
249 })
250 }
251
252 fn contains_prefix(&self, prefix: &str) -> bool {
253 self.visible_entries().any(|(k, _)| k.starts_with(prefix))
254 }
255
256 fn iter_prefix<'b>(&'b self, prefix: &'b str) -> Box<dyn Iterator<Item = (&'b str, &'b Property)> + 'b> {
257 Box::new(self.visible_entries().filter(move |(k, _)| k.starts_with(prefix)))
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}