Mercurial > hg
changeset 46598:bc08c2331f99
rust: Add a `ConfigValueParseError` variant to common errors
Configuration files are parsed into sections of key/value pairs when
they are read, but at that point values are still arbitrary bytes.
Only when a value is accessed by various parts of the code do we know
its expected type and syntax, so values are parsed at that point.
Let’s make a new error type for this latter kind of parsing error,
and add a variant to the common `HgError` so that most code can propagate it
without much boilerplate.
Differential Revision: https://phab.mercurial-scm.org/D10009
author | Simon Sapin <simon.sapin@octobus.net> |
---|---|
date | Tue, 16 Feb 2021 15:22:20 +0100 |
parents | 305d74c262de |
children | 1f55cd5b292f |
files | rust/hg-core/src/config.rs rust/hg-core/src/config/config.rs rust/hg-core/src/errors.rs |
diffstat | 3 files changed, 60 insertions(+), 16 deletions(-) [+] |
line wrap: on
line diff
--- a/rust/hg-core/src/config.rs Tue Feb 16 13:55:31 2021 +0100 +++ b/rust/hg-core/src/config.rs Tue Feb 16 15:22:20 2021 +0100 @@ -11,5 +11,5 @@ mod config; mod layer; -pub use config::Config; +pub use config::{Config, ConfigValueParseError}; pub use layer::{ConfigError, ConfigParseError};
--- a/rust/hg-core/src/config/config.rs Tue Feb 16 13:55:31 2021 +0100 +++ b/rust/hg-core/src/config/config.rs Tue Feb 16 15:22:20 2021 +0100 @@ -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<usize>, + pub section: Vec<u8>, + pub item: Vec<u8>, + pub value: Vec<u8>, + pub expected_type: &'static str, +} + pub fn parse_bool(v: &[u8]) -> Option<bool> { 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<T>, - ) -> Result<Option<T>, ConfigParseError> { + ) -> Result<Option<T>, 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<Option<&str>, ConfigParseError> { - self.get_parse(section, item, |value| str::from_utf8(value).ok()) + ) -> Result<Option<&str>, 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<Option<u32>, ConfigParseError> { - self.get_parse(section, item, |value| { + ) -> Result<Option<u32>, 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<Option<u64>, ConfigParseError> { - self.get_parse(section, item, parse_byte_size) + ) -> Result<Option<u64>, 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<Option<bool>, ConfigParseError> { - self.get_parse(section, item, parse_bool) + ) -> Result<Option<bool>, 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<bool, ConfigError> { + ) -> Result<bool, ConfigValueParseError> { Ok(self.get_option(section, item)?.unwrap_or(false)) }
--- a/rust/hg-core/src/errors.rs Tue Feb 16 13:55:31 2021 +0100 +++ b/rust/hg-core/src/errors.rs Tue Feb 16 15:22:20 2021 +0100 @@ -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) + ) + } } } }