diff --git a/rust/hg-core/src/config.rs b/rust/hg-core/src/config.rs --- a/rust/hg-core/src/config.rs +++ b/rust/hg-core/src/config.rs @@ -11,5 +11,5 @@ mod config; mod layer; -pub use config::Config; +pub use config::{Config, ConfigValueParseError}; pub use layer::{ConfigError, ConfigParseError}; diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -9,7 +9,7 @@ use super::layer; use crate::config::layer::{ - ConfigError, ConfigLayer, ConfigParseError, ConfigValue, + ConfigError, ConfigLayer, ConfigOrigin, ConfigValue, }; use crate::utils::files::get_bytes_from_os_str; use format_bytes::{write_bytes, DisplayBytes}; @@ -54,6 +54,16 @@ Parsed(layer::ConfigLayer), } +#[derive(Debug)] +pub struct ConfigValueParseError { + pub origin: ConfigOrigin, + pub line: Option, + pub section: Vec, + pub item: Vec, + pub value: Vec, + pub expected_type: &'static str, +} + pub fn parse_bool(v: &[u8]) -> Option { match v.to_ascii_lowercase().as_slice() { b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true), @@ -262,15 +272,19 @@ &'config self, section: &[u8], item: &[u8], + expected_type: &'static str, parse: impl Fn(&'config [u8]) -> Option, - ) -> Result, ConfigParseError> { + ) -> Result, ConfigValueParseError> { match self.get_inner(§ion, &item) { Some((layer, v)) => match parse(&v.bytes) { Some(b) => Ok(Some(b)), - None => Err(ConfigParseError { + None => Err(ConfigValueParseError { origin: layer.origin.to_owned(), line: v.line, - bytes: v.bytes.to_owned(), + value: v.bytes.to_owned(), + section: section.to_owned(), + item: item.to_owned(), + expected_type, }), }, None => Ok(None), @@ -283,8 +297,10 @@ &self, section: &[u8], item: &[u8], - ) -> Result, ConfigParseError> { - self.get_parse(section, item, |value| str::from_utf8(value).ok()) + ) -> Result, ConfigValueParseError> { + self.get_parse(section, item, "ASCII or UTF-8 string", |value| { + str::from_utf8(value).ok() + }) } /// Returns an `Err` if the first value found is not a valid unsigned @@ -293,8 +309,8 @@ &self, section: &[u8], item: &[u8], - ) -> Result, ConfigParseError> { - self.get_parse(section, item, |value| { + ) -> Result, ConfigValueParseError> { + self.get_parse(section, item, "valid integer", |value| { str::from_utf8(value).ok()?.parse().ok() }) } @@ -306,8 +322,8 @@ &self, section: &[u8], item: &[u8], - ) -> Result, ConfigParseError> { - self.get_parse(section, item, parse_byte_size) + ) -> Result, ConfigValueParseError> { + self.get_parse(section, item, "byte quantity", parse_byte_size) } /// Returns an `Err` if the first value found is not a valid boolean. @@ -317,8 +333,8 @@ &self, section: &[u8], item: &[u8], - ) -> Result, ConfigParseError> { - self.get_parse(section, item, parse_bool) + ) -> Result, ConfigValueParseError> { + self.get_parse(section, item, "boolean", parse_bool) } /// Returns the corresponding boolean in the config. Returns `Ok(false)` @@ -327,7 +343,7 @@ &self, section: &[u8], item: &[u8], - ) -> Result { + ) -> Result { Ok(self.get_option(section, item)?.unwrap_or(false)) } diff --git a/rust/hg-core/src/errors.rs b/rust/hg-core/src/errors.rs --- a/rust/hg-core/src/errors.rs +++ b/rust/hg-core/src/errors.rs @@ -1,7 +1,8 @@ +use crate::config::ConfigValueParseError; use std::fmt; /// Common error cases that can happen in many different APIs -#[derive(Debug)] +#[derive(Debug, derive_more::From)] pub enum HgError { IoError { error: std::io::Error, @@ -29,6 +30,14 @@ /// The given string is a short explanation for users, not intended to be /// machine-readable. Abort(String), + + /// A configuration value is not in the expected syntax. + /// + /// These errors can happen in many places in the code because values are + /// parsed lazily as the file-level parser does not know the expected type + /// and syntax of each value. + #[from] + ConfigValueParseError(ConfigValueParseError), } /// Details about where an I/O error happened @@ -63,6 +72,7 @@ impl fmt::Display for HgError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + HgError::Abort(explanation) => write!(f, "{}", explanation), HgError::IoError { error, context } => { write!(f, "{}: {}", error, context) } @@ -72,7 +82,25 @@ HgError::UnsupportedFeature(explanation) => { write!(f, "unsupported feature: {}", explanation) } - HgError::Abort(explanation) => explanation.fmt(f), + HgError::ConfigValueParseError(ConfigValueParseError { + origin: _, + line: _, + section, + item, + value, + expected_type, + }) => { + // TODO: add origin and line number information, here and in + // corresponding python code + write!( + f, + "config error: {}.{} is not a {} ('{}')", + String::from_utf8_lossy(section), + String::from_utf8_lossy(item), + expected_type, + String::from_utf8_lossy(value) + ) + } } } }