Skip to main content

gix_config/file/access/
comfort.rs

1use bstr::{BStr, BString};
2use gix_error::ExnMessageResult;
3use gix_error::{ErrorExt, validation};
4
5use crate::{AsBStrOpt, AsKey, File, file::Metadata};
6
7/// Comfortable API for accessing values
8impl File {
9    /// Like [`string_by()`](File::string_by()), but suitable for statically known `key`s like `remote.origin.url`.
10    pub fn string(&self, key: impl AsKey) -> Option<BString> {
11        self.string_filter(key, |_| true)
12    }
13
14    /// Like [`value()`](File::value()), but returning `None` if the string wasn't found.
15    ///
16    /// As strings perform no conversions, this will never fail.
17    pub fn string_by(
18        &self,
19        section_name: impl AsRef<str>,
20        subsection_name: impl AsBStrOpt,
21        value_name: impl AsRef<str>,
22    ) -> Option<BString> {
23        self.string_filter_by(section_name, subsection_name, value_name, |_| true)
24    }
25
26    /// Like [`string_filter_by()`](File::string_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
27    pub fn string_filter(&self, key: impl AsKey, filter: impl FnMut(&Metadata) -> bool) -> Option<BString> {
28        let key = key.try_as_key()?;
29        self.raw_value_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
30            .ok()
31    }
32
33    /// Like [`string()`](File::string()), but the section containing the returned value must pass `filter` as well.
34    pub fn string_filter_by(
35        &self,
36        section_name: impl AsRef<str>,
37        subsection_name: impl AsBStrOpt,
38        value_name: impl AsRef<str>,
39        filter: impl FnMut(&Metadata) -> bool,
40    ) -> Option<BString> {
41        self.raw_value_filter_by(section_name, subsection_name, value_name, filter)
42            .ok()
43    }
44
45    /// Like [`path_by()`](File::path_by()), but suitable for statically known `key`s like `remote.origin.url`.
46    pub fn path(&self, key: impl AsKey) -> Option<crate::Path> {
47        self.path_filter(key, |_| true)
48    }
49
50    /// Like [`value()`](File::value()), but returning `None` if the path wasn't found.
51    ///
52    /// Note that this path is not vetted and should only point to resources which can't be used
53    /// to pose a security risk. Prefer using [`path_filter()`](File::path_filter()) instead.
54    ///
55    /// As paths perform no conversions, this will never fail.
56    pub fn path_by(
57        &self,
58        section_name: impl AsRef<str>,
59        subsection_name: impl AsBStrOpt,
60        value_name: impl AsRef<str>,
61    ) -> Option<crate::Path> {
62        self.path_filter_by(section_name, subsection_name, value_name, |_| true)
63    }
64
65    /// Like [`path_filter_by()`](File::path_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
66    pub fn path_filter(&self, key: impl AsKey, filter: impl FnMut(&Metadata) -> bool) -> Option<crate::Path> {
67        let key = key.try_as_key()?;
68        self.path_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
69    }
70
71    /// Like [`path()`](File::path()), but the section containing the returned value must pass `filter` as well.
72    ///
73    /// This should be the preferred way of accessing paths as those from untrusted
74    /// locations can be
75    ///
76    /// As paths perform no conversions, this will never fail.
77    pub fn path_filter_by(
78        &self,
79        section_name: impl AsRef<str>,
80        subsection_name: impl AsBStrOpt,
81        value_name: impl AsRef<str>,
82        filter: impl FnMut(&Metadata) -> bool,
83    ) -> Option<crate::Path> {
84        self.raw_value_filter_by(section_name, subsection_name, value_name, filter)
85            .ok()
86            .map(crate::Path::from)
87    }
88
89    /// Like [`boolean_by()`](File::boolean_by()), but suitable for statically known `key`s like `remote.origin.url`.
90    pub fn boolean(&self, key: impl AsKey) -> ExnMessageResult<Option<bool>> {
91        self.boolean_filter(key, |_| true)
92    }
93
94    /// Like [`value()`](File::value()), but returning `None` if the boolean value wasn't found.
95    pub fn boolean_by(
96        &self,
97        section_name: impl AsRef<str>,
98        subsection_name: impl AsBStrOpt,
99        value_name: impl AsRef<str>,
100    ) -> ExnMessageResult<Option<bool>> {
101        self.boolean_filter_by(section_name, subsection_name, value_name, |_| true)
102    }
103
104    /// Like [`boolean_filter_by()`](File::boolean_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
105    pub fn boolean_filter(
106        &self,
107        key: impl AsKey,
108        filter: impl FnMut(&Metadata) -> bool,
109    ) -> ExnMessageResult<Option<bool>> {
110        let Some(key) = key.try_as_key() else {
111            return Ok(None);
112        };
113        self.boolean_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
114    }
115
116    /// Like [`boolean_by()`](File::boolean_by()), but the section containing the returned value must pass `filter` as well.
117    pub fn boolean_filter_by(
118        &self,
119        section_name: impl AsRef<str>,
120        subsection_name: impl AsBStrOpt,
121        value_name: impl AsRef<str>,
122        mut filter: impl FnMut(&Metadata) -> bool,
123    ) -> ExnMessageResult<Option<bool>> {
124        let section_name = section_name.as_ref();
125        let section_ids = self
126            .section_ids_by_name_and_subname(section_name, subsection_name.as_bstr_opt())
127            .ok();
128        let Some(section_ids) = section_ids else {
129            return Ok(None);
130        };
131        let key = value_name.as_ref();
132        for section_id in section_ids.rev() {
133            let section = self.sections.get(&section_id).expect("known section id");
134            if !filter(section.meta()) {
135                continue;
136            }
137            match section.body.value_implicit_in(&self.backing, key) {
138                Some(Some(v)) => return crate::Boolean::try_from(v).map(|value| Some(value.into())),
139                Some(None) => return Ok(Some(true)),
140                None => continue,
141            }
142        }
143        Ok(None)
144    }
145
146    /// Like [`integer_by()`](File::integer_by()), but suitable for statically known `key`s like `remote.origin.url`.
147    pub fn integer(&self, key: impl AsKey) -> ExnMessageResult<Option<i64>> {
148        self.integer_filter(key, |_| true)
149    }
150
151    /// Like [`value()`](File::value()), but returning an `Option` if the integer wasn't found.
152    pub fn integer_by(
153        &self,
154        section_name: impl AsRef<str>,
155        subsection_name: impl AsBStrOpt,
156        value_name: impl AsRef<str>,
157    ) -> ExnMessageResult<Option<i64>> {
158        self.integer_filter_by(section_name, subsection_name, value_name, |_| true)
159    }
160
161    /// Like [`integer_filter_by()`](File::integer_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
162    pub fn integer_filter(
163        &self,
164        key: impl AsKey,
165        filter: impl FnMut(&Metadata) -> bool,
166    ) -> ExnMessageResult<Option<i64>> {
167        let Some(key) = key.try_as_key() else {
168            return Ok(None);
169        };
170        self.integer_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
171    }
172
173    /// Like [`integer_by()`](File::integer_by()), but the section containing the returned value must pass `filter` as well.
174    /// Invalid or overflowing values include their bytes as `input` [metadata](gix_error::Exn::metadata()).
175    pub fn integer_filter_by(
176        &self,
177        section_name: impl AsRef<str>,
178        subsection_name: impl AsBStrOpt,
179        value_name: impl AsRef<str>,
180        filter: impl FnMut(&Metadata) -> bool,
181    ) -> ExnMessageResult<Option<i64>> {
182        let Some(int) = self
183            .raw_value_filter_by(section_name, subsection_name, value_name, filter)
184            .ok()
185        else {
186            return Ok(None);
187        };
188        crate::Integer::try_from(BStr::new(&int))
189            .and_then(|b| {
190                b.to_decimal()
191                    .ok_or_else(|| validation("Integer overflow").with("input", BStr::new(&int)).raise())
192            })
193            .map(Some)
194    }
195
196    /// Like [`strings_by()`](File::strings_by()), but suitable for statically known `key`s like `remote.origin.url`.
197    pub fn strings(&self, key: impl AsKey) -> Option<Vec<BString>> {
198        let key = key.try_as_key()?;
199        self.strings_by(key.section_name, key.subsection_name, key.value_name)
200    }
201
202    /// Similar to [`values_by(…)`](File::values_by()) but returning strings if at least one of them was found.
203    pub fn strings_by(
204        &self,
205        section_name: impl AsRef<str>,
206        subsection_name: impl AsBStrOpt,
207        value_name: impl AsRef<str>,
208    ) -> Option<Vec<BString>> {
209        self.raw_values_by(section_name, subsection_name, value_name).ok()
210    }
211
212    /// Like [`strings_filter_by()`](File::strings_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
213    pub fn strings_filter(&self, key: impl AsKey, filter: impl FnMut(&Metadata) -> bool) -> Option<Vec<BString>> {
214        let key = key.try_as_key()?;
215        self.strings_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
216    }
217
218    /// Similar to [`strings_by(…)`](File::strings_by()), but all values are in sections that passed `filter`.
219    pub fn strings_filter_by(
220        &self,
221        section_name: impl AsRef<str>,
222        subsection_name: impl AsBStrOpt,
223        value_name: impl AsRef<str>,
224        filter: impl FnMut(&Metadata) -> bool,
225    ) -> Option<Vec<BString>> {
226        self.raw_values_filter_by(section_name, subsection_name, value_name, filter)
227            .ok()
228    }
229
230    /// Like [`integers()`](File::integers()), but suitable for statically known `key`s like `remote.origin.url`.
231    pub fn integers(&self, key: impl AsKey) -> ExnMessageResult<Option<Vec<i64>>> {
232        self.integers_filter(key, |_| true)
233    }
234
235    /// Similar to [`values_by(…)`](File::values_by()) but returning integers if at least one of them was found
236    /// and if none of them overflows.
237    pub fn integers_by(
238        &self,
239        section_name: impl AsRef<str>,
240        subsection_name: impl AsBStrOpt,
241        value_name: impl AsRef<str>,
242    ) -> ExnMessageResult<Option<Vec<i64>>> {
243        self.integers_filter_by(section_name, subsection_name, value_name, |_| true)
244    }
245
246    /// Like [`integers_filter_by()`](File::integers_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
247    pub fn integers_filter(
248        &self,
249        key: impl AsKey,
250        filter: impl FnMut(&Metadata) -> bool,
251    ) -> ExnMessageResult<Option<Vec<i64>>> {
252        let Some(key) = key.try_as_key() else {
253            return Ok(None);
254        };
255        self.integers_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
256    }
257
258    /// Similar to [`integers_by(…)`](File::integers_by()) but all integers are in sections that passed `filter`
259    /// and that are not overflowing.
260    /// Invalid or overflowing values include their bytes as `input` [metadata](gix_error::Exn::metadata()).
261    pub fn integers_filter_by(
262        &self,
263        section_name: impl AsRef<str>,
264        subsection_name: impl AsBStrOpt,
265        value_name: impl AsRef<str>,
266        filter: impl FnMut(&Metadata) -> bool,
267    ) -> ExnMessageResult<Option<Vec<i64>>> {
268        let Some(values) = self
269            .raw_values_filter_by(section_name, subsection_name, value_name, filter)
270            .ok()
271        else {
272            return Ok(None);
273        };
274        values
275            .into_iter()
276            .map(|v| {
277                crate::Integer::try_from(BStr::new(&v)).and_then(|int| {
278                    int.to_decimal()
279                        .ok_or_else(|| validation("Integer overflow").with("input", BStr::new(&v)).raise())
280                })
281            })
282            .collect::<Result<Vec<_>, _>>()
283            .map(Some)
284    }
285}