gix/config/tree/
keys.rs

1#![allow(clippy::result_large_err)]
2use std::{
3    borrow::Cow,
4    error::Error,
5    fmt::{Debug, Formatter},
6};
7
8use gix_config::KeyRef;
9
10use crate::{
11    bstr::BStr,
12    config,
13    config::tree::{Key, Link, Note, Section, SubSectionRequirement},
14};
15
16/// Implements a value without any constraints, i.e. a any value.
17pub struct Any<T: Validate = validate::All> {
18    /// The key of the value in the git configuration.
19    pub name: &'static str,
20    /// The parent section of the key.
21    pub section: &'static dyn Section,
22    /// The subsection requirement to use.
23    pub subsection_requirement: Option<SubSectionRequirement>,
24    /// A link to other resources that might be eligible as value.
25    pub link: Option<Link>,
26    /// A note about this key.
27    pub note: Option<Note>,
28    /// The way validation and transformation should happen.
29    validate: T,
30}
31
32/// Init
33impl Any<validate::All> {
34    /// Create a new instance from `name` and `section`
35    pub const fn new(name: &'static str, section: &'static dyn Section) -> Self {
36        Any::new_with_validate(name, section, validate::All)
37    }
38}
39
40/// Init other validate implementations
41impl<T: Validate> Any<T> {
42    /// Create a new instance from `name` and `section`
43    pub const fn new_with_validate(name: &'static str, section: &'static dyn Section, validate: T) -> Self {
44        Any {
45            name,
46            section,
47            subsection_requirement: Some(SubSectionRequirement::Never),
48            link: None,
49            note: None,
50            validate,
51        }
52    }
53}
54
55/// Builder
56impl<T: Validate> Any<T> {
57    /// Set the subsection requirement to non-default values.
58    pub const fn with_subsection_requirement(mut self, requirement: Option<SubSectionRequirement>) -> Self {
59        self.subsection_requirement = requirement;
60        self
61    }
62
63    /// Associate an environment variable with this key.
64    ///
65    /// This is mainly useful for enriching error messages.
66    pub const fn with_environment_override(mut self, var: &'static str) -> Self {
67        self.link = Some(Link::EnvironmentOverride(var));
68        self
69    }
70
71    /// Set a link to another key which serves as fallback to provide a value if this key is not set.
72    pub const fn with_fallback(mut self, key: &'static dyn Key) -> Self {
73        self.link = Some(Link::FallbackKey(key));
74        self
75    }
76
77    /// Attach an informative message to this key.
78    pub const fn with_note(mut self, message: &'static str) -> Self {
79        self.note = Some(Note::Informative(message));
80        self
81    }
82
83    /// Inform about a deviation in how this key is interpreted.
84    pub const fn with_deviation(mut self, message: &'static str) -> Self {
85        self.note = Some(Note::Deviation(message));
86        self
87    }
88}
89
90/// Conversion
91impl<T: Validate> Any<T> {
92    /// Try to convert `value` into a refspec suitable for the `op` operation.
93    pub fn try_into_refspec(
94        &'static self,
95        value: std::borrow::Cow<'_, BStr>,
96        op: gix_refspec::parse::Operation,
97    ) -> Result<gix_refspec::RefSpec, config::refspec::Error> {
98        gix_refspec::parse(value.as_ref(), op)
99            .map(|spec| spec.to_owned())
100            .map_err(|err| config::refspec::Error::from_value(self, value.into_owned()).with_source(err))
101    }
102
103    /// Try to interpret `value` as UTF-8 encoded string.
104    pub fn try_into_string(&'static self, value: Cow<'_, BStr>) -> Result<std::string::String, config::string::Error> {
105        use crate::bstr::ByteVec;
106        Vec::from(value.into_owned()).into_string().map_err(|err| {
107            let utf8_err = err.utf8_error().clone();
108            config::string::Error::from_value(self, err.into_vec().into()).with_source(utf8_err)
109        })
110    }
111}
112
113impl<T: Validate> Debug for Any<T> {
114    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115        self.logical_name().fmt(f)
116    }
117}
118
119impl<T: Validate> std::fmt::Display for Any<T> {
120    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121        f.write_str(&self.logical_name())
122    }
123}
124
125impl<T: Validate> Key for Any<T> {
126    fn name(&self) -> &str {
127        self.name
128    }
129
130    fn validate(&self, value: &BStr) -> Result<(), config::tree::key::validate::Error> {
131        Ok(self.validate.validate(value)?)
132    }
133
134    fn section(&self) -> &dyn Section {
135        self.section
136    }
137
138    fn subsection_requirement(&self) -> Option<&SubSectionRequirement> {
139        self.subsection_requirement.as_ref()
140    }
141
142    fn link(&self) -> Option<&Link> {
143        self.link.as_ref()
144    }
145
146    fn note(&self) -> Option<&Note> {
147        self.note.as_ref()
148    }
149}
150
151impl<T: Validate> gix_config::AsKey for Any<T> {
152    fn as_key(&self) -> gix_config::KeyRef<'_> {
153        self.try_as_key().expect("infallible")
154    }
155
156    fn try_as_key(&self) -> Option<KeyRef<'_>> {
157        let section_name = self.section.parent().map_or_else(|| self.section.name(), Section::name);
158        let subsection_name = if self.section.parent().is_some() {
159            Some(self.section.name().into())
160        } else {
161            None
162        };
163        let value_name = self.name;
164        gix_config::KeyRef {
165            section_name,
166            subsection_name,
167            value_name,
168        }
169        .into()
170    }
171}
172
173/// A key which represents a date.
174pub type Time = Any<validate::Time>;
175
176/// The `core.(filesRefLockTimeout|packedRefsTimeout)` keys, or any other lock timeout for that matter.
177pub type LockTimeout = Any<validate::LockTimeout>;
178
179/// Keys specifying durations in milliseconds.
180pub type DurationInMilliseconds = Any<validate::DurationInMilliseconds>;
181
182/// A key which represents any unsigned integer.
183pub type UnsignedInteger = Any<validate::UnsignedInteger>;
184
185/// A key that represents a remote name, either as url or symbolic name.
186pub type RemoteName = Any<validate::RemoteName>;
187
188/// A key that represents a boolean value.
189pub type Boolean = Any<validate::Boolean>;
190
191/// A key that represents an executable program, shell script or shell commands.
192///
193/// Once obtained with [trusted_program()](crate::config::Snapshot::trusted_program())
194/// one can run it with [command::prepare()](gix_command::prepare), possibly after
195/// [obtaining](crate::Repository::command_context) and [setting](gix_command::Prepare::with_context)
196/// a git [command context](gix_command::Context) (depending on the commands needs).
197pub type Program = Any<validate::Program>;
198
199/// A key that represents an executable program as identified by name or path.
200///
201/// Once obtained with [trusted_program()](crate::config::Snapshot::trusted_program())
202/// one can run it with [command::prepare()](gix_command::prepare), possibly after
203/// [obtaining](crate::Repository::command_context) and [setting](gix_command::Prepare::with_context)
204/// a git [command context](gix_command::Context) (depending on the commands needs).
205pub type Executable = Any<validate::Executable>;
206
207/// A key that represents a path (to a resource).
208pub type Path = Any<validate::Path>;
209
210/// A key that represents a URL.
211pub type Url = Any<validate::Url>;
212
213/// A key that represents a UTF-8 string.
214pub type String = Any<validate::String>;
215
216/// A key that represents a `RefSpec` for pushing.
217pub type PushRefSpec = Any<validate::PushRefSpec>;
218
219/// A key that represents a `RefSpec` for fetching.
220pub type FetchRefSpec = Any<validate::FetchRefSpec>;
221
222mod duration {
223    use std::time::Duration;
224
225    use crate::{
226        config,
227        config::tree::{keys::DurationInMilliseconds, Section},
228    };
229
230    impl DurationInMilliseconds {
231        /// Create a new instance.
232        pub const fn new_duration(name: &'static str, section: &'static dyn Section) -> Self {
233            Self::new_with_validate(name, section, super::validate::DurationInMilliseconds)
234        }
235
236        /// Return a valid duration as parsed from an integer that is interpreted as milliseconds.
237        pub fn try_into_duration(
238            &'static self,
239            value: Result<i64, gix_config::value::Error>,
240        ) -> Result<std::time::Duration, config::duration::Error> {
241            let value = value.map_err(|err| config::duration::Error::from(self).with_source(err))?;
242            Ok(match value {
243                val if val < 0 => Duration::from_secs(u64::MAX),
244                val => Duration::from_millis(val.try_into().expect("i64 to u64 always works if positive")),
245            })
246        }
247    }
248}
249
250mod lock_timeout {
251    use std::time::Duration;
252
253    use gix_lock::acquire::Fail;
254
255    use crate::{
256        config,
257        config::tree::{keys::LockTimeout, Section},
258    };
259
260    impl LockTimeout {
261        /// Create a new instance.
262        pub const fn new_lock_timeout(name: &'static str, section: &'static dyn Section) -> Self {
263            Self::new_with_validate(name, section, super::validate::LockTimeout)
264        }
265
266        /// Return information on how long to wait for locked files.
267        pub fn try_into_lock_timeout(
268            &'static self,
269            value: Result<i64, gix_config::value::Error>,
270        ) -> Result<gix_lock::acquire::Fail, config::lock_timeout::Error> {
271            let value = value.map_err(|err| config::lock_timeout::Error::from(self).with_source(err))?;
272            Ok(match value {
273                val if val < 0 => Fail::AfterDurationWithBackoff(Duration::from_secs(u64::MAX)),
274                0 => Fail::Immediately,
275                val => Fail::AfterDurationWithBackoff(Duration::from_millis(
276                    val.try_into().expect("i64 to u64 always works if positive"),
277                )),
278            })
279        }
280    }
281}
282
283mod refspecs {
284    use crate::config::tree::{
285        keys::{validate, FetchRefSpec, PushRefSpec},
286        Section,
287    };
288
289    impl PushRefSpec {
290        /// Create a new instance.
291        pub const fn new_push_refspec(name: &'static str, section: &'static dyn Section) -> Self {
292            Self::new_with_validate(name, section, validate::PushRefSpec)
293        }
294    }
295
296    impl FetchRefSpec {
297        /// Create a new instance.
298        pub const fn new_fetch_refspec(name: &'static str, section: &'static dyn Section) -> Self {
299            Self::new_with_validate(name, section, validate::FetchRefSpec)
300        }
301    }
302}
303
304mod url {
305    use std::borrow::Cow;
306
307    use crate::{
308        bstr::BStr,
309        config,
310        config::tree::{
311            keys::{validate, Url},
312            Section,
313        },
314    };
315
316    impl Url {
317        /// Create a new instance.
318        pub const fn new_url(name: &'static str, section: &'static dyn Section) -> Self {
319            Self::new_with_validate(name, section, validate::Url)
320        }
321
322        /// Try to parse `value` as URL.
323        pub fn try_into_url(&'static self, value: Cow<'_, BStr>) -> Result<gix_url::Url, config::url::Error> {
324            gix_url::parse(value.as_ref())
325                .map_err(|err| config::url::Error::from_value(self, value.into_owned()).with_source(err))
326        }
327    }
328}
329
330impl String {
331    /// Create a new instance.
332    pub const fn new_string(name: &'static str, section: &'static dyn Section) -> Self {
333        Self::new_with_validate(name, section, validate::String)
334    }
335}
336
337impl Program {
338    /// Create a new instance.
339    pub const fn new_program(name: &'static str, section: &'static dyn Section) -> Self {
340        Self::new_with_validate(name, section, validate::Program)
341    }
342}
343
344impl Executable {
345    /// Create a new instance.
346    pub const fn new_executable(name: &'static str, section: &'static dyn Section) -> Self {
347        Self::new_with_validate(name, section, validate::Executable)
348    }
349}
350
351impl Path {
352    /// Create a new instance.
353    pub const fn new_path(name: &'static str, section: &'static dyn Section) -> Self {
354        Self::new_with_validate(name, section, validate::Path)
355    }
356}
357
358mod workers {
359    use crate::config::tree::{keys::UnsignedInteger, Section};
360
361    impl UnsignedInteger {
362        /// Create a new instance.
363        pub const fn new_unsigned_integer(name: &'static str, section: &'static dyn Section) -> Self {
364            Self::new_with_validate(name, section, super::validate::UnsignedInteger)
365        }
366
367        /// Convert `value` into a `usize` or wrap it into a specialized error.
368        pub fn try_into_usize(
369            &'static self,
370            value: Result<i64, gix_config::value::Error>,
371        ) -> Result<usize, crate::config::unsigned_integer::Error> {
372            value
373                .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err))
374                .and_then(|value| {
375                    value
376                        .try_into()
377                        .map_err(|_| crate::config::unsigned_integer::Error::from(self))
378                })
379        }
380
381        /// Convert `value` into a `u64` or wrap it into a specialized error.
382        pub fn try_into_u64(
383            &'static self,
384            value: Result<i64, gix_config::value::Error>,
385        ) -> Result<u64, crate::config::unsigned_integer::Error> {
386            value
387                .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err))
388                .and_then(|value| {
389                    value
390                        .try_into()
391                        .map_err(|_| crate::config::unsigned_integer::Error::from(self))
392                })
393        }
394
395        /// Convert `value` into a `u32` or wrap it into a specialized error.
396        pub fn try_into_u32(
397            &'static self,
398            value: Result<i64, gix_config::value::Error>,
399        ) -> Result<u32, crate::config::unsigned_integer::Error> {
400            value
401                .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err))
402                .and_then(|value| {
403                    value
404                        .try_into()
405                        .map_err(|_| crate::config::unsigned_integer::Error::from(self))
406                })
407        }
408    }
409}
410
411mod time {
412    use std::borrow::Cow;
413
414    use crate::{
415        bstr::{BStr, ByteSlice},
416        config::tree::{
417            keys::{validate, Time},
418            Section,
419        },
420    };
421
422    impl Time {
423        /// Create a new instance.
424        pub const fn new_time(name: &'static str, section: &'static dyn Section) -> Self {
425            Self::new_with_validate(name, section, validate::Time)
426        }
427
428        /// Convert the `value` into a date if possible, with `now` as reference time for relative dates.
429        pub fn try_into_time(
430            &self,
431            value: Cow<'_, BStr>,
432            now: Option<std::time::SystemTime>,
433        ) -> Result<gix_date::Time, gix_date::parse::Error> {
434            gix_date::parse(
435                value
436                    .as_ref()
437                    .to_str()
438                    .map_err(|_| gix_date::parse::Error::InvalidDateString {
439                        input: value.to_string(),
440                    })?,
441                now,
442            )
443        }
444    }
445}
446
447mod boolean {
448    use crate::{
449        config,
450        config::tree::{
451            keys::{validate, Boolean},
452            Section,
453        },
454    };
455
456    impl Boolean {
457        /// Create a new instance.
458        pub const fn new_boolean(name: &'static str, section: &'static dyn Section) -> Self {
459            Self::new_with_validate(name, section, validate::Boolean)
460        }
461
462        /// Process the `value` into a result with an improved error message.
463        ///
464        /// `value` is expected to be provided by [`gix_config::File::boolean()`].
465        pub fn enrich_error(
466            &'static self,
467            value: Result<bool, gix_config::value::Error>,
468        ) -> Result<bool, config::boolean::Error> {
469            value.map_err(|err| config::boolean::Error::from(self).with_source(err))
470        }
471    }
472}
473
474mod remote_name {
475    use std::borrow::Cow;
476
477    use crate::{
478        bstr::{BStr, BString},
479        config,
480        config::tree::{keys::RemoteName, Section},
481    };
482
483    impl RemoteName {
484        /// Create a new instance.
485        pub const fn new_remote_name(name: &'static str, section: &'static dyn Section) -> Self {
486            Self::new_with_validate(name, section, super::validate::RemoteName)
487        }
488
489        /// Try to validate `name` as symbolic remote name and return it.
490        #[allow(clippy::result_large_err)]
491        pub fn try_into_symbolic_name(
492            &'static self,
493            name: Cow<'_, BStr>,
494        ) -> Result<BString, config::remote::symbolic_name::Error> {
495            crate::remote::name::validated(name.into_owned())
496                .map_err(|err| config::remote::symbolic_name::Error::from(self).with_source(err))
497        }
498    }
499}
500
501/// Provide a way to validate a value, or decode a value from `git-config`.
502pub trait Validate {
503    /// Validate `value` or return an error.
504    fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>>;
505}
506
507/// various implementations of the `Validate` trait.
508pub mod validate {
509    use std::{borrow::Cow, error::Error};
510
511    use crate::{
512        bstr::{BStr, ByteSlice},
513        config::tree::keys::Validate,
514        remote,
515    };
516
517    /// Everything is valid.
518    #[derive(Default)]
519    pub struct All;
520
521    impl Validate for All {
522        fn validate(&self, _value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
523            Ok(())
524        }
525    }
526
527    /// Assure that values that parse as git dates are valid.
528    #[derive(Default)]
529    pub struct Time;
530
531    impl Validate for Time {
532        fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
533            gix_date::parse(value.to_str()?, std::time::SystemTime::now().into())?;
534            Ok(())
535        }
536    }
537
538    /// Assure that values that parse as unsigned integers are valid.
539    #[derive(Default)]
540    pub struct UnsignedInteger;
541
542    impl Validate for UnsignedInteger {
543        fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
544            usize::try_from(
545                gix_config::Integer::try_from(value)?
546                    .to_decimal()
547                    .ok_or_else(|| format!("integer {value} cannot be represented as `usize`"))?,
548            )
549            .map_err(|_| "cannot use sign for unsigned integer")?;
550            Ok(())
551        }
552    }
553
554    /// Assure that values that parse as git booleans are valid.
555    #[derive(Default)]
556    pub struct Boolean;
557
558    impl Validate for Boolean {
559        fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
560            gix_config::Boolean::try_from(value)?;
561            Ok(())
562        }
563    }
564
565    /// Values that are git remotes, symbolic or urls
566    #[derive(Default)]
567    pub struct RemoteName;
568    impl Validate for RemoteName {
569        fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
570            remote::Name::try_from(Cow::Borrowed(value))
571                .map_err(|_| format!("Illformed UTF-8 in remote name: \"{}\"", value.to_str_lossy()))?;
572            Ok(())
573        }
574    }
575
576    /// Values that are programs - everything is allowed.
577    #[derive(Default)]
578    pub struct Program;
579    impl Validate for Program {
580        fn validate(&self, _value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
581            Ok(())
582        }
583    }
584
585    /// Values that are programs executables, everything is allowed.
586    #[derive(Default)]
587    pub struct Executable;
588    impl Validate for Executable {
589        fn validate(&self, _value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
590            Ok(())
591        }
592    }
593
594    /// Values that parse as URLs.
595    #[derive(Default)]
596    pub struct Url;
597    impl Validate for Url {
598        fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
599            gix_url::parse(value)?;
600            Ok(())
601        }
602    }
603
604    /// Values that parse as ref-specs for pushing.
605    #[derive(Default)]
606    pub struct PushRefSpec;
607    impl Validate for PushRefSpec {
608        fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
609            gix_refspec::parse(value, gix_refspec::parse::Operation::Push)?;
610            Ok(())
611        }
612    }
613
614    /// Values that parse as ref-specs for pushing.
615    #[derive(Default)]
616    pub struct FetchRefSpec;
617    impl Validate for FetchRefSpec {
618        fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
619            gix_refspec::parse(value, gix_refspec::parse::Operation::Fetch)?;
620            Ok(())
621        }
622    }
623
624    /// Timeouts used for file locks.
625    pub struct LockTimeout;
626    impl Validate for LockTimeout {
627        fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
628            let value = gix_config::Integer::try_from(value)?
629                .to_decimal()
630                .ok_or_else(|| format!("integer {value} cannot be represented as integer"));
631            super::super::Core::FILES_REF_LOCK_TIMEOUT.try_into_lock_timeout(Ok(value?))?;
632            Ok(())
633        }
634    }
635
636    /// Durations in milliseconds.
637    pub struct DurationInMilliseconds;
638    impl Validate for DurationInMilliseconds {
639        fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
640            let value = gix_config::Integer::try_from(value)?
641                .to_decimal()
642                .ok_or_else(|| format!("integer {value} cannot be represented as integer"));
643            super::super::gitoxide::Http::CONNECT_TIMEOUT.try_into_duration(Ok(value?))?;
644            Ok(())
645        }
646    }
647
648    /// A UTF-8 string.
649    pub struct String;
650    impl Validate for String {
651        fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
652            value.to_str()?;
653            Ok(())
654        }
655    }
656
657    /// Any path - everything is allowed.
658    pub struct Path;
659    impl Validate for Path {
660        fn validate(&self, _value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
661            Ok(())
662        }
663    }
664}