Support for a convenient way of knowing if a value does or doesn't exist via `try_value()`, which can only fail if the conversion fails. Lastly, `string()` is a special case which doesn't fail as there is no conversion, and `boolean()` allows to obtain a plain boolean value if it was a valid boolean representation.
Unified · Split
| @@ -1,3 +1,4 @@ | ||
| 1 | +use bstr::BStr; | |
| 1 | 2 | use std::{ |
| 2 | 3 | borrow::Cow, |
| 3 | 4 | collections::{HashMap, VecDeque}, |
| @@ -441,6 +442,42 @@ impl<'event> GitConfig<'event> { · 437 unmodified lines | ||
| 441 | 442 | T::try_from(self.raw_value(section_name, subsection_name, key)?).map_err(lookup::Error::FailedConversion) |
| 442 | 443 | } |
| 443 | 444 | |
| 445 | + /// Like [`value()`][GitConfig::value()], but returning an `Option` if the value wasn't found. | |
| 446 | + pub fn try_value<T: TryFrom<Cow<'event, [u8]>>>( | |
| 447 | + &'event self, | |
| 448 | + section_name: &str, | |
| 449 | + subsection_name: Option<&str>, | |
| 450 | + key: &str, | |
| 451 | + ) -> Option<Result<T, T::Error>> { | |
| 452 | + self.raw_value(section_name, subsection_name, key).ok().map(T::try_from) | |
| 453 | + } | |
| 454 | + | |
| 455 | + /// Like [`value()`][GitConfig::value()], but returning an `Option` if the string wasn't found. | |
| 456 | + /// | |
| 457 | + /// As strings perform no conversions, this will never fail. | |
| 458 | + pub fn string( | |
| 459 | + &'event self, | |
| 460 | + section_name: &str, | |
| 461 | + subsection_name: Option<&str>, | |
| 462 | + key: &str, | |
| 463 | + ) -> Option<Cow<'event, BStr>> { | |
| 464 | + self.raw_value(section_name, subsection_name, key) | |
| 465 | + .ok() | |
| 466 | + .map(|v| values::String::from(v).value) | |
| 467 | + } | |
| 468 | + | |
| 469 | + /// Like [`value()`][GitConfig::value()], but returning an `Option` if the boolean wasn't found. | |
| 470 | + pub fn boolean( | |
| 471 | + &'event self, | |
| 472 | + section_name: &str, | |
| 473 | + subsection_name: Option<&str>, | |
| 474 | + key: &str, | |
| 475 | + ) -> Option<Result<bool, values::BooleanError>> { | |
| 476 | + self.raw_value(section_name, subsection_name, key) | |
| 477 | + .ok() | |
| 478 | + .map(|v| values::Boolean::try_from(v).map(|b| b.to_bool())) | |
| 479 | + } | |
| 480 | + | |
| 444 | 481 | /// Returns all interpreted values given a section, an optional subsection |
| 445 | 482 | /// and key. |
| 446 | 483 | /// |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; |
| 4 | 4 | |
| 5 | −use bstr::BStr; | |
| 5 | +use bstr::{BStr, BString}; | |
| 6 | 6 | #[cfg(feature = "serde")] |
| 7 | 7 | use serde::{Serialize, Serializer}; |
| 8 | 8 | |
| @@ -377,9 +377,9 @@ impl Boolean<'_> { · 368 unmodified lines | ||
| 377 | 377 | /// The error returned when creating `Boolean` from byte string. |
| 378 | 378 | #[derive(Debug, PartialEq, thiserror::Error)] |
| 379 | 379 | #[allow(missing_docs)] |
| 380 | −pub enum BooleanError { | |
| 381 | − #[error("Invalid argument format")] | |
| 382 | − InvalidFormat, | |
| 380 | +#[error("Invalid boolean value: '{}'", .input)] | |
| 381 | +pub struct BooleanError { | |
| 382 | + pub input: BString, | |
| 383 | 383 | } |
| 384 | 384 | |
| 385 | 385 | impl<'a> TryFrom<&'a [u8]> for Boolean<'a> { |
| @@ -401,7 +401,7 @@ impl<'a> TryFrom<&'a [u8]> for Boolean<'a> { · 15 unmodified lines | ||
| 401 | 401 | )); |
| 402 | 402 | } |
| 403 | 403 | |
| 404 | − Err(BooleanError::InvalidFormat) | |
| 404 | + Err(BooleanError { input: value.into() }) | |
| 405 | 405 | } |
| 406 | 406 | } |
| 407 | 407 | |
| @@ -512,7 +512,7 @@ impl<'a> TryFrom<&'a [u8]> for TrueVariant<'a> { · 104 unmodified lines | ||
| 512 | 512 | } else if value.is_empty() { |
| 513 | 513 | Ok(Self::Implicit) |
| 514 | 514 | } else { |
| 515 | − Err(BooleanError::InvalidFormat) | |
| 515 | + Err(BooleanError { input: value.into() }) | |
| 516 | 516 | } |
| 517 | 517 | } |
| 518 | 518 | } |
| @@ -532,7 +532,7 @@ impl TryFrom<Vec<u8>> for TrueVariant<'_> { · 13 unmodified lines | ||
| 532 | 532 | } else if value.is_empty() { |
| 533 | 533 | Ok(Self::Implicit) |
| 534 | 534 | } else { |
| 535 | − Err(BooleanError::InvalidFormat) | |
| 535 | + Err(BooleanError { input: value.into() }) | |
| 536 | 536 | } |
| 537 | 537 | } |
| 538 | 538 | } |
| @@ -23,11 +23,20 @@ fn get_value_for_all_provided_values() -> crate::Result { · 22 unmodified lines | ||
| 23 | 23 | file.value::<Boolean>("core", None, "bool-explicit")?, |
| 24 | 24 | Boolean::False(Cow::Borrowed("false")) |
| 25 | 25 | ); |
| 26 | + assert_eq!(file.boolean("core", None, "bool-explicit").expect("exists")?, false); | |
| 26 | 27 | |
| 27 | 28 | assert_eq!( |
| 28 | 29 | file.value::<Boolean>("core", None, "bool-implicit")?, |
| 29 | 30 | Boolean::True(TrueVariant::Implicit) |
| 30 | 31 | ); |
| 32 | + assert_eq!( | |
| 33 | + file.try_value::<Boolean>("core", None, "bool-implicit") | |
| 34 | + .expect("exists")?, | |
| 35 | + Boolean::True(TrueVariant::Implicit) | |
| 36 | + ); | |
| 37 | + | |
| 38 | + assert_eq!(file.boolean("core", None, "bool-implicit").expect("present")?, true); | |
| 39 | + assert_eq!(file.try_value::<String>("doesnt", None, "exist"), None); | |
| 31 | 40 | |
| 32 | 41 | assert_eq!( |
| 33 | 42 | file.value::<Integer>("core", None, "integer-no-prefix")?, |
| @@ -69,6 +78,11 @@ fn get_value_for_all_provided_values() -> crate::Result { · 35 unmodified lines | ||
| 69 | 78 | } |
| 70 | 79 | ); |
| 71 | 80 | |
| 81 | + assert_eq!( | |
| 82 | + file.string("core", None, "other").expect("present").as_ref(), | |
| 83 | + "hello world" | |
| 84 | + ); | |
| 85 | + | |
| 72 | 86 | let actual = file.value::<git_config::values::Path>("core", None, "location")?; |
| 73 | 87 | assert_eq!( |
| 74 | 88 | &*actual, |