use super::ConfigPath;
use crate::{Config, ConfigError, ConfigWarning, ShellError, ShellWarning, Span, Type, Value};
#[derive(Debug)]
#[must_use]
pub(super) struct ConfigErrors<'a> {
config: &'a Config,
history_locked_after_startup: bool,
errors: Vec<ConfigError>,
warnings: Vec<ConfigWarning>,
}
impl<'a> ConfigErrors<'a> {
pub fn new(config: &'a Config) -> Self {
Self {
config,
history_locked_after_startup: false,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn with_history_locked_after_startup(mut self, locked: bool) -> Self {
self.history_locked_after_startup = locked;
self
}
pub fn config(&self) -> &Config {
self.config
}
pub fn history_locked_after_startup(&self) -> bool {
self.history_locked_after_startup
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
pub fn error(&mut self, error: ConfigError) {
self.errors.push(error);
}
pub fn warn(&mut self, warning: ConfigWarning) {
self.warnings.push(warning);
}
pub fn type_mismatch(&mut self, path: &ConfigPath, expected: Type, actual: &Value) {
let actual_ty = actual.get_type();
let help = match (&expected, &actual_ty) {
(Type::Record(..), Type::Closure | Type::Block) => {
"If you meant a record, use `key: value` fields (colon required). \
A missing `:` makes `{ … }` parse as a closure/block."
.into()
}
_ => format!(
"Check the value assigned to {path}. Expected type: {expected}, found: {actual_ty}."
),
};
self.error(ConfigError::TypeMismatch {
path: path.to_string(),
expected,
actual: actual_ty,
span: actual.span(),
help,
});
}
pub fn invalid_value(
&mut self,
path: &ConfigPath,
expected: impl Into<String>,
actual: &Value,
) {
self.error(ConfigError::InvalidValue {
path: path.to_string(),
valid: expected.into(),
actual: if let Ok(str) = actual.as_str() {
format!("'{str}'")
} else {
actual.to_abbreviated_string(self.config)
},
span: actual.span(),
});
}
pub fn missing_column(&mut self, path: &ConfigPath, column: &'static str, span: Span) {
self.error(ConfigError::MissingRequiredColumn {
path: path.to_string(),
column,
span,
})
}
pub fn unknown_option(&mut self, path: &ConfigPath, value: &Value) {
self.error(ConfigError::UnknownOption {
path: path.to_string(),
span: value.span(),
});
}
pub fn locked_after_startup(&mut self, path: &ConfigPath, span: Span) {
self.error(ConfigError::LockedAfterStartup {
path: path.to_string(),
span,
});
}
#[allow(dead_code)]
pub fn deprecated_option(&mut self, path: &ConfigPath, suggestion: &'static str, span: Span) {
self.error(ConfigError::Deprecated {
path: path.to_string(),
suggestion,
span,
});
}
pub fn check(self) -> Result<Option<ShellWarning>, ShellError> {
match (self.has_errors(), self.has_warnings()) {
(true, _) => Err(ShellError::InvalidConfig {
errors: self.errors,
}),
(false, true) => Ok(Some(ShellWarning::InvalidConfig {
warnings: self.warnings,
})),
(false, false) => Ok(None),
}
}
}