| #![allow(clippy::result_large_err)] |
| use std::{ |
| borrow::Cow, |
| error::Error, |
| fmt::{Debug, Formatter}, |
| }; |
| |
| use crate::{ |
| bstr::BStr, |
| config, |
| config::tree::{Key, Link, Note, Section, SubSectionRequirement}, |
| }; |
| |
| /// Implements a value without any constraints, i.e. a any value. |
| pub struct Any<T: Validate = validate::All> { |
| /// The key of the value in the git configuration. |
| pub name: &'static str, |
| /// The parent section of the key. |
| pub section: &'static dyn Section, |
| /// The subsection requirement to use. |
| pub subsection_requirement: Option<SubSectionRequirement>, |
| /// A link to other resources that might be eligible as value. |
| pub link: Option<Link>, |
| /// A note about this key. |
| pub note: Option<Note>, |
| /// The way validation and transformation should happen. |
| validate: T, |
| } |
| |
| /// Init |
| impl Any<validate::All> { |
| /// Create a new instance from `name` and `section` |
| pub const fn new(name: &'static str, section: &'static dyn Section) -> Self { |
| Any::new_with_validate(name, section, validate::All) |
| } |
| } |
| |
| /// Init other validate implementations |
| impl<T: Validate> Any<T> { |
| /// Create a new instance from `name` and `section` |
| pub const fn new_with_validate(name: &'static str, section: &'static dyn Section, validate: T) -> Self { |
| Any { |
| name, |
| section, |
| subsection_requirement: Some(SubSectionRequirement::Never), |
| link: None, |
| note: None, |
| validate, |
| } |
| } |
| } |
| |
| /// Builder |
| impl<T: Validate> Any<T> { |
| /// Set the subsection requirement to non-default values. |
| pub const fn with_subsection_requirement(mut self, requirement: Option<SubSectionRequirement>) -> Self { |
| self.subsection_requirement = requirement; |
| self |
| } |
| |
| /// Associate an environment variable with this key. |
| /// |
| /// This is mainly useful for enriching error messages. |
| pub const fn with_environment_override(mut self, var: &'static str) -> Self { |
| self.link = Some(Link::EnvironmentOverride(var)); |
| self |
| } |
| |
| /// Set a link to another key which serves as fallback to provide a value if this key is not set. |
| pub const fn with_fallback(mut self, key: &'static dyn Key) -> Self { |
| self.link = Some(Link::FallbackKey(key)); |
| self |
| } |
| |
| /// Attach an informative message to this key. |
| pub const fn with_note(mut self, message: &'static str) -> Self { |
| self.note = Some(Note::Informative(message)); |
| self |
| } |
| |
| /// Inform about a deviation in how this key is interpreted. |
| pub const fn with_deviation(mut self, message: &'static str) -> Self { |
| self.note = Some(Note::Deviation(message)); |
| self |
| } |
| } |
| |
| /// Conversion |
| impl<T: Validate> Any<T> { |
| /// Try to convert `value` into a refspec suitable for the `op` operation. |
| pub fn try_into_refspec( |
| &'static self, |
| value: std::borrow::Cow<'_, BStr>, |
| op: gix_refspec::parse::Operation, |
| ) -> Result<gix_refspec::RefSpec, config::refspec::Error> { |
| gix_refspec::parse(value.as_ref(), op) |
| .map(|spec| spec.to_owned()) |
| .map_err(|err| config::refspec::Error::from_value(self, value.into_owned()).with_source(err)) |
| } |
| |
| /// Try to interpret `value` as UTF-8 encoded string. |
| pub fn try_into_string(&'static self, value: Cow<'_, BStr>) -> Result<std::string::String, config::string::Error> { |
| use crate::bstr::ByteVec; |
| Vec::from(value.into_owned()).into_string().map_err(|err| { |
| let utf8_err = err.utf8_error().clone(); |
| config::string::Error::from_value(self, err.into_vec().into()).with_source(utf8_err) |
| }) |
| } |
| } |
| |
| impl<T: Validate> Debug for Any<T> { |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| self.logical_name().fmt(f) |
| } |
| } |
| |
| impl<T: Validate> std::fmt::Display for Any<T> { |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| f.write_str(&self.logical_name()) |
| } |
| } |
| |
| impl<T: Validate> Key for Any<T> { |
| fn name(&self) -> &str { |
| self.name |
| } |
| |
| fn validate(&self, value: &BStr) -> Result<(), config::tree::key::validate::Error> { |
| Ok(self.validate.validate(value)?) |
| } |
| |
| fn section(&self) -> &dyn Section { |
| self.section |
| } |
| |
| fn subsection_requirement(&self) -> Option<&SubSectionRequirement> { |
| self.subsection_requirement.as_ref() |
| } |
| |
| fn link(&self) -> Option<&Link> { |
| self.link.as_ref() |
| } |
| |
| fn note(&self) -> Option<&Note> { |
| self.note.as_ref() |
| } |
| } |
| |
| /// A key which represents a date. |
| pub type Time = Any<validate::Time>; |
| |
| /// The `core.(filesRefLockTimeout|packedRefsTimeout)` keys, or any other lock timeout for that matter. |
| pub type LockTimeout = Any<validate::LockTimeout>; |
| |
| /// Keys specifying durations in milliseconds. |
| pub type DurationInMilliseconds = Any<validate::DurationInMilliseconds>; |
| |
| /// A key which represents any unsigned integer. |
| pub type UnsignedInteger = Any<validate::UnsignedInteger>; |
| |
| /// A key that represents a remote name, either as url or symbolic name. |
| pub type RemoteName = Any<validate::RemoteName>; |
| |
| /// A key that represents a boolean value. |
| pub type Boolean = Any<validate::Boolean>; |
| |
| /// A key that represents an executable program, shell script or shell commands. |
| pub type Program = Any<validate::Program>; |
| |
| /// A key that represents an executable program as identified by name or path. |
| pub type Executable = Any<validate::Executable>; |
| |
| /// A key that represents a path (to a resource). |
| pub type Path = Any<validate::Path>; |
| |
| /// A key that represents a URL. |
| pub type Url = Any<validate::Url>; |
| |
| /// A key that represents a UTF-8 string. |
| pub type String = Any<validate::String>; |
| |
| /// A key that represents a `RefSpec` for pushing. |
| pub type PushRefSpec = Any<validate::PushRefSpec>; |
| |
| /// A key that represents a `RefSpec` for fetching. |
| pub type FetchRefSpec = Any<validate::FetchRefSpec>; |
| |
| mod duration { |
| use std::time::Duration; |
| |
| use crate::{ |
| config, |
| config::tree::{keys::DurationInMilliseconds, Section}, |
| }; |
| |
| impl DurationInMilliseconds { |
| /// Create a new instance. |
| pub const fn new_duration(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, super::validate::DurationInMilliseconds) |
| } |
| |
| /// Return a valid duration as parsed from an integer that is interpreted as milliseconds. |
| pub fn try_into_duration( |
| &'static self, |
| value: Result<i64, gix_config::value::Error>, |
| ) -> Result<std::time::Duration, config::duration::Error> { |
| let value = value.map_err(|err| config::duration::Error::from(self).with_source(err))?; |
| Ok(match value { |
| val if val < 0 => Duration::from_secs(u64::MAX), |
| val => Duration::from_millis(val.try_into().expect("i64 to u64 always works if positive")), |
| }) |
| } |
| } |
| } |
| |
| mod lock_timeout { |
| use std::time::Duration; |
| |
| use gix_lock::acquire::Fail; |
| |
| use crate::{ |
| config, |
| config::tree::{keys::LockTimeout, Section}, |
| }; |
| |
| impl LockTimeout { |
| /// Create a new instance. |
| pub const fn new_lock_timeout(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, super::validate::LockTimeout) |
| } |
| |
| /// Return information on how long to wait for locked files. |
| pub fn try_into_lock_timeout( |
| &'static self, |
| value: Result<i64, gix_config::value::Error>, |
| ) -> Result<gix_lock::acquire::Fail, config::lock_timeout::Error> { |
| let value = value.map_err(|err| config::lock_timeout::Error::from(self).with_source(err))?; |
| Ok(match value { |
| val if val < 0 => Fail::AfterDurationWithBackoff(Duration::from_secs(u64::MAX)), |
| 0 => Fail::Immediately, |
| val => Fail::AfterDurationWithBackoff(Duration::from_millis( |
| val.try_into().expect("i64 to u64 always works if positive"), |
| )), |
| }) |
| } |
| } |
| } |
| |
| mod refspecs { |
| use crate::config::tree::{ |
| keys::{validate, FetchRefSpec, PushRefSpec}, |
| Section, |
| }; |
| |
| impl PushRefSpec { |
| /// Create a new instance. |
| pub const fn new_push_refspec(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::PushRefSpec) |
| } |
| } |
| |
| impl FetchRefSpec { |
| /// Create a new instance. |
| pub const fn new_fetch_refspec(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::FetchRefSpec) |
| } |
| } |
| } |
| |
| mod url { |
| use std::borrow::Cow; |
| |
| use crate::{ |
| bstr::BStr, |
| config, |
| config::tree::{ |
| keys::{validate, Url}, |
| Section, |
| }, |
| }; |
| |
| impl Url { |
| /// Create a new instance. |
| pub const fn new_url(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::Url) |
| } |
| |
| /// Try to parse `value` as URL. |
| pub fn try_into_url(&'static self, value: Cow<'_, BStr>) -> Result<gix_url::Url, config::url::Error> { |
| gix_url::parse(value.as_ref()) |
| .map_err(|err| config::url::Error::from_value(self, value.into_owned()).with_source(err)) |
| } |
| } |
| } |
| |
| impl String { |
| /// Create a new instance. |
| pub const fn new_string(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::String) |
| } |
| } |
| |
| impl Program { |
| /// Create a new instance. |
| pub const fn new_program(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::Program) |
| } |
| } |
| |
| impl Executable { |
| /// Create a new instance. |
| pub const fn new_executable(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::Executable) |
| } |
| } |
| |
| impl Path { |
| /// Create a new instance. |
| pub const fn new_path(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::Path) |
| } |
| } |
| |
| mod workers { |
| use crate::config::tree::{keys::UnsignedInteger, Section}; |
| |
| impl UnsignedInteger { |
| /// Create a new instance. |
| pub const fn new_unsigned_integer(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, super::validate::UnsignedInteger) |
| } |
| |
| /// Convert `value` into a `usize` or wrap it into a specialized error. |
| pub fn try_into_usize( |
| &'static self, |
| value: Result<i64, gix_config::value::Error>, |
| ) -> Result<usize, crate::config::unsigned_integer::Error> { |
| value |
| .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err)) |
| .and_then(|value| { |
| value |
| .try_into() |
| .map_err(|_| crate::config::unsigned_integer::Error::from(self)) |
| }) |
| } |
| |
| /// Convert `value` into a `u64` or wrap it into a specialized error. |
| pub fn try_into_u64( |
| &'static self, |
| value: Result<i64, gix_config::value::Error>, |
| ) -> Result<u64, crate::config::unsigned_integer::Error> { |
| value |
| .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err)) |
| .and_then(|value| { |
| value |
| .try_into() |
| .map_err(|_| crate::config::unsigned_integer::Error::from(self)) |
| }) |
| } |
| |
| /// Convert `value` into a `u32` or wrap it into a specialized error. |
| pub fn try_into_u32( |
| &'static self, |
| value: Result<i64, gix_config::value::Error>, |
| ) -> Result<u32, crate::config::unsigned_integer::Error> { |
| value |
| .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err)) |
| .and_then(|value| { |
| value |
| .try_into() |
| .map_err(|_| crate::config::unsigned_integer::Error::from(self)) |
| }) |
| } |
| } |
| } |
| |
| mod time { |
| use std::borrow::Cow; |
| |
| use crate::{ |
| bstr::{BStr, ByteSlice}, |
| config::tree::{ |
| keys::{validate, Time}, |
| Section, |
| }, |
| }; |
| |
| impl Time { |
| /// Create a new instance. |
| pub const fn new_time(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::Time) |
| } |
| |
| /// Convert the `value` into a date if possible, with `now` as reference time for relative dates. |
| pub fn try_into_time( |
| &self, |
| value: Cow<'_, BStr>, |
| now: Option<std::time::SystemTime>, |
| ) -> Result<gix_date::Time, gix_date::parse::Error> { |
| gix_date::parse( |
| value |
| .as_ref() |
| .to_str() |
| .map_err(|_| gix_date::parse::Error::InvalidDateString { |
| input: value.to_string(), |
| })?, |
| now, |
| ) |
| } |
| } |
| } |
| |
| mod boolean { |
| use crate::{ |
| config, |
| config::tree::{ |
| keys::{validate, Boolean}, |
| Section, |
| }, |
| }; |
| |
| impl Boolean { |
| /// Create a new instance. |
| pub const fn new_boolean(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, validate::Boolean) |
| } |
| |
| /// Process the `value` into a result with an improved error message. |
| /// |
| /// `value` is expected to be provided by [`gix_config::File::boolean()`]. |
| pub fn enrich_error( |
| &'static self, |
| value: Result<bool, gix_config::value::Error>, |
| ) -> Result<bool, config::boolean::Error> { |
| value.map_err(|err| config::boolean::Error::from(self).with_source(err)) |
| } |
| } |
| } |
| |
| mod remote_name { |
| use std::borrow::Cow; |
| |
| use crate::{ |
| bstr::{BStr, BString}, |
| config, |
| config::tree::{keys::RemoteName, Section}, |
| }; |
| |
| impl RemoteName { |
| /// Create a new instance. |
| pub const fn new_remote_name(name: &'static str, section: &'static dyn Section) -> Self { |
| Self::new_with_validate(name, section, super::validate::RemoteName) |
| } |
| |
| /// Try to validate `name` as symbolic remote name and return it. |
| #[allow(clippy::result_large_err)] |
| pub fn try_into_symbolic_name( |
| &'static self, |
| name: Cow<'_, BStr>, |
| ) -> Result<BString, config::remote::symbolic_name::Error> { |
| crate::remote::name::validated(name.into_owned()) |
| .map_err(|err| config::remote::symbolic_name::Error::from(self).with_source(err)) |
| } |
| } |
| } |
| |
| /// Provide a way to validate a value, or decode a value from `git-config`. |
| pub trait Validate { |
| /// Validate `value` or return an error. |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>>; |
| } |
| |
| /// various implementations of the `Validate` trait. |
| pub mod validate { |
| use std::{borrow::Cow, error::Error}; |
| |
| use crate::{ |
| bstr::{BStr, ByteSlice}, |
| config::tree::keys::Validate, |
| remote, |
| }; |
| |
| /// Everything is valid. |
| #[derive(Default)] |
| pub struct All; |
| |
| impl Validate for All { |
| fn validate(&self, _value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { |
| Ok(()) |
| } |
| } |
| |
| /// Assure that values that parse as git dates are valid. |
| #[derive(Default)] |
| pub struct Time; |
| |
| impl Validate for Time { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { |
| gix_date::parse(value.to_str()?, std::time::SystemTime::now().into())?; |
| Ok(()) |
| } |
| } |
| |
| /// Assure that values that parse as unsigned integers are valid. |
| #[derive(Default)] |
| pub struct UnsignedInteger; |
| |
| impl Validate for UnsignedInteger { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { |
| usize::try_from( |
| gix_config::Integer::try_from(value)? |
| .to_decimal() |
| .ok_or_else(|| format!("integer {value} cannot be represented as `usize`"))?, |
| ) |
| .map_err(|_| "cannot use sign for unsigned integer")?; |
| Ok(()) |
| } |
| } |
| |
| /// Assure that values that parse as git booleans are valid. |
| #[derive(Default)] |
| pub struct Boolean; |
| |
| impl Validate for Boolean { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { |
| gix_config::Boolean::try_from(value)?; |
| Ok(()) |
| } |
| } |
| |
| /// Values that are git remotes, symbolic or urls |
| #[derive(Default)] |
| pub struct RemoteName; |
| impl Validate for RemoteName { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| remote::Name::try_from(Cow::Borrowed(value)) |
| .map_err(|_| format!("Illformed UTF-8 in remote name: \"{}\"", value.to_str_lossy()))?; |
| Ok(()) |
| } |
| } |
| |
| /// Values that are programs - everything is allowed. |
| #[derive(Default)] |
| pub struct Program; |
| impl Validate for Program { |
| fn validate(&self, _value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| Ok(()) |
| } |
| } |
| |
| /// Values that are programs executables, everything is allowed. |
| #[derive(Default)] |
| pub struct Executable; |
| impl Validate for Executable { |
| fn validate(&self, _value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| Ok(()) |
| } |
| } |
| |
| /// Values that parse as URLs. |
| #[derive(Default)] |
| pub struct Url; |
| impl Validate for Url { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| gix_url::parse(value)?; |
| Ok(()) |
| } |
| } |
| |
| /// Values that parse as ref-specs for pushing. |
| #[derive(Default)] |
| pub struct PushRefSpec; |
| impl Validate for PushRefSpec { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| gix_refspec::parse(value, gix_refspec::parse::Operation::Push)?; |
| Ok(()) |
| } |
| } |
| |
| /// Values that parse as ref-specs for pushing. |
| #[derive(Default)] |
| pub struct FetchRefSpec; |
| impl Validate for FetchRefSpec { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| gix_refspec::parse(value, gix_refspec::parse::Operation::Fetch)?; |
| Ok(()) |
| } |
| } |
| |
| /// Timeouts used for file locks. |
| pub struct LockTimeout; |
| impl Validate for LockTimeout { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| let value = gix_config::Integer::try_from(value)? |
| .to_decimal() |
| .ok_or_else(|| format!("integer {value} cannot be represented as integer")); |
| super::super::Core::FILES_REF_LOCK_TIMEOUT.try_into_lock_timeout(Ok(value?))?; |
| Ok(()) |
| } |
| } |
| |
| /// Durations in milliseconds. |
| pub struct DurationInMilliseconds; |
| impl Validate for DurationInMilliseconds { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| let value = gix_config::Integer::try_from(value)? |
| .to_decimal() |
| .ok_or_else(|| format!("integer {value} cannot be represented as integer")); |
| super::super::gitoxide::Http::CONNECT_TIMEOUT.try_into_duration(Ok(value?))?; |
| Ok(()) |
| } |
| } |
| |
| /// A UTF-8 string. |
| pub struct String; |
| impl Validate for String { |
| fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| value.to_str()?; |
| Ok(()) |
| } |
| } |
| |
| /// Any path - everything is allowed. |
| pub struct Path; |
| impl Validate for Path { |
| fn validate(&self, _value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
| Ok(()) |
| } |
| } |
| } |