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
16pub struct Any<T: Validate = validate::All> {
18 pub name: &'static str,
20 pub section: &'static dyn Section,
22 pub subsection_requirement: Option<SubSectionRequirement>,
24 pub link: Option<Link>,
26 pub note: Option<Note>,
28 validate: T,
30}
31
32impl Any<validate::All> {
34 pub const fn new(name: &'static str, section: &'static dyn Section) -> Self {
36 Any::new_with_validate(name, section, validate::All)
37 }
38}
39
40impl<T: Validate> Any<T> {
42 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
55impl<T: Validate> Any<T> {
57 pub const fn with_subsection_requirement(mut self, requirement: Option<SubSectionRequirement>) -> Self {
59 self.subsection_requirement = requirement;
60 self
61 }
62
63 pub const fn with_environment_override(mut self, var: &'static str) -> Self {
67 self.link = Some(Link::EnvironmentOverride(var));
68 self
69 }
70
71 pub const fn with_fallback(mut self, key: &'static dyn Key) -> Self {
73 self.link = Some(Link::FallbackKey(key));
74 self
75 }
76
77 pub const fn with_note(mut self, message: &'static str) -> Self {
79 self.note = Some(Note::Informative(message));
80 self
81 }
82
83 pub const fn with_deviation(mut self, message: &'static str) -> Self {
85 self.note = Some(Note::Deviation(message));
86 self
87 }
88}
89
90impl<T: Validate> Any<T> {
92 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 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
173pub type Time = Any<validate::Time>;
175
176pub type LockTimeout = Any<validate::LockTimeout>;
178
179pub type DurationInMilliseconds = Any<validate::DurationInMilliseconds>;
181
182pub type UnsignedInteger = Any<validate::UnsignedInteger>;
184
185pub type RemoteName = Any<validate::RemoteName>;
187
188pub type Boolean = Any<validate::Boolean>;
190
191pub type Program = Any<validate::Program>;
198
199pub type Executable = Any<validate::Executable>;
206
207pub type Path = Any<validate::Path>;
209
210pub type Url = Any<validate::Url>;
212
213pub type String = Any<validate::String>;
215
216pub type PushRefSpec = Any<validate::PushRefSpec>;
218
219pub 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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
501pub trait Validate {
503 fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>>;
505}
506
507pub 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 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 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}