mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 17:05:03 +02:00
Add warning when using history isolation with non-SQLite history format (#16151)
# Description This PR depends on #16147, use `git diff 132ikl/shell-warning 132ikl/isolation-warn` to see only changes from this PR People seem to get tripped up by this a lot, and it's not exactly intuitive, so I added a warning if you try to set `$env.config.history.isolation = true` when using the plaintext file format: Warning: nu:🐚:invalid_config ⚠ Encountered 1 warnings(s) when updating config Warning: nu:🐚:incompatible_options ⚠ Incompatible options ╭─[source:1:33] 1 │ $env.config.history.isolation = true · ──┬─ · ╰── history isolation only compatible with SQLite format ╰──── help: disable history isolation, or set $env.config.history.file_format = "sqlite" # User-Facing Changes * Added a warning when using history isolation without using SQLite history. # Tests + Formatting Added a test
This commit is contained in:
@ -1,10 +1,12 @@
|
|||||||
use super::ConfigPath;
|
use super::ConfigPath;
|
||||||
use crate::{Config, ConfigError, ShellError, Span, Type, Value};
|
use crate::{Config, ConfigError, ConfigWarning, ShellError, ShellWarning, Span, Type, Value};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[must_use]
|
||||||
pub(super) struct ConfigErrors<'a> {
|
pub(super) struct ConfigErrors<'a> {
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
errors: Vec<ConfigError>,
|
errors: Vec<ConfigError>,
|
||||||
|
warnings: Vec<ConfigWarning>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ConfigErrors<'a> {
|
impl<'a> ConfigErrors<'a> {
|
||||||
@ -12,17 +14,26 @@ impl<'a> ConfigErrors<'a> {
|
|||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
errors: Vec::new(),
|
errors: Vec::new(),
|
||||||
|
warnings: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn has_errors(&self) -> bool {
|
||||||
self.errors.is_empty()
|
!self.errors.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_warnings(&self) -> bool {
|
||||||
|
!self.warnings.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn error(&mut self, error: ConfigError) {
|
pub fn error(&mut self, error: ConfigError) {
|
||||||
self.errors.push(error);
|
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) {
|
pub fn type_mismatch(&mut self, path: &ConfigPath, expected: Type, actual: &Value) {
|
||||||
self.error(ConfigError::TypeMismatch {
|
self.error(ConfigError::TypeMismatch {
|
||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
@ -75,13 +86,15 @@ impl<'a> ConfigErrors<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_shell_error(self) -> Option<ShellError> {
|
pub fn check(self) -> Result<Option<ShellWarning>, ShellError> {
|
||||||
if self.is_empty() {
|
match (self.has_errors(), self.has_warnings()) {
|
||||||
None
|
(true, _) => Err(ShellError::InvalidConfig {
|
||||||
} else {
|
|
||||||
Some(ShellError::InvalidConfig {
|
|
||||||
errors: self.errors,
|
errors: self.errors,
|
||||||
})
|
}),
|
||||||
|
(false, true) => Ok(Some(ShellWarning::InvalidConfig {
|
||||||
|
warnings: self.warnings,
|
||||||
|
})),
|
||||||
|
(false, false) => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::{config_update_string_enum, prelude::*};
|
use super::{config_update_string_enum, prelude::*};
|
||||||
use crate as nu_protocol;
|
use crate::{self as nu_protocol, ConfigWarning};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum HistoryFileFormat {
|
pub enum HistoryFileFormat {
|
||||||
@ -77,15 +77,35 @@ impl UpdateFromValue for HistoryConfig {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// might not be correct if file format was changed away from sqlite rather than isolation,
|
||||||
|
// but this is an edge case and the span of the relevant value here should be close enough
|
||||||
|
let mut isolation_span = value.span();
|
||||||
|
|
||||||
for (col, val) in record.iter() {
|
for (col, val) in record.iter() {
|
||||||
let path = &mut path.push(col);
|
let path = &mut path.push(col);
|
||||||
match col.as_str() {
|
match col.as_str() {
|
||||||
"isolation" => self.isolation.update(val, path, errors),
|
"isolation" => {
|
||||||
|
isolation_span = val.span();
|
||||||
|
self.isolation.update(val, path, errors)
|
||||||
|
}
|
||||||
"sync_on_enter" => self.sync_on_enter.update(val, path, errors),
|
"sync_on_enter" => self.sync_on_enter.update(val, path, errors),
|
||||||
"max_size" => self.max_size.update(val, path, errors),
|
"max_size" => self.max_size.update(val, path, errors),
|
||||||
"file_format" => self.file_format.update(val, path, errors),
|
"file_format" => self.file_format.update(val, path, errors),
|
||||||
_ => errors.unknown_option(path, val),
|
_ => errors.unknown_option(path, val),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listing all formats separately in case additional ones are added
|
||||||
|
match (self.isolation, self.file_format) {
|
||||||
|
(true, HistoryFileFormat::Plaintext) => {
|
||||||
|
errors.warn(ConfigWarning::IncompatibleOptions {
|
||||||
|
label: "history isolation only compatible with SQLite format",
|
||||||
|
span: isolation_span,
|
||||||
|
help: r#"disable history isolation, or set $env.config.history.file_format = "sqlite""#,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
(true, HistoryFileFormat::Sqlite) => (),
|
||||||
|
(false, _) => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Module containing the internal representation of user configuration
|
//! Module containing the internal representation of user configuration
|
||||||
|
|
||||||
use crate as nu_protocol;
|
|
||||||
use crate::FromValue;
|
use crate::FromValue;
|
||||||
|
use crate::{self as nu_protocol};
|
||||||
use helper::*;
|
use helper::*;
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -219,7 +219,11 @@ impl UpdateFromValue for Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn update_from_value(&mut self, old: &Config, value: &Value) -> Option<ShellError> {
|
pub fn update_from_value(
|
||||||
|
&mut self,
|
||||||
|
old: &Config,
|
||||||
|
value: &Value,
|
||||||
|
) -> Result<Option<ShellWarning>, ShellError> {
|
||||||
// Current behaviour is that config errors are displayed, but do not prevent the rest
|
// Current behaviour is that config errors are displayed, but do not prevent the rest
|
||||||
// of the config from being updated (fields with errors are skipped/not updated).
|
// of the config from being updated (fields with errors are skipped/not updated).
|
||||||
// Errors are simply collected one-by-one and wrapped into a ShellError variant at the end.
|
// Errors are simply collected one-by-one and wrapped into a ShellError variant at the end.
|
||||||
@ -228,6 +232,6 @@ impl Config {
|
|||||||
|
|
||||||
self.update(value, &mut path, &mut errors);
|
self.update(value, &mut path, &mut errors);
|
||||||
|
|
||||||
errors.into_shell_error()
|
errors.check()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ mod tests {
|
|||||||
let mut errors = ConfigErrors::new(&config);
|
let mut errors = ConfigErrors::new(&config);
|
||||||
let mut result = PluginGcConfigs::default();
|
let mut result = PluginGcConfigs::default();
|
||||||
result.update(&input, &mut ConfigPath::new(), &mut errors);
|
result.update(&input, &mut ConfigPath::new(), &mut errors);
|
||||||
assert!(errors.is_empty(), "errors: {errors:#?}");
|
assert!(!errors.has_errors(), "errors: {errors:#?}");
|
||||||
assert_eq!(expected, result);
|
assert_eq!(expected, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
pub(super) use super::{ConfigPath, UpdateFromValue, error::ConfigErrors};
|
pub(super) use super::{ConfigPath, UpdateFromValue, error::ConfigErrors};
|
||||||
pub use crate::{IntoValue, ShellError, Span, Type, Value, record};
|
pub use crate::{IntoValue, ShellError, ShellWarning, Span, Type, Value, record};
|
||||||
pub use serde::{Deserialize, Serialize};
|
pub use serde::{Deserialize, Serialize};
|
||||||
pub use std::str::FromStr;
|
pub use std::str::FromStr;
|
||||||
|
@ -4,6 +4,7 @@ use crate::{
|
|||||||
ArgumentStack, DEFAULT_OVERLAY_NAME, EngineState, ErrorHandlerStack, Redirection,
|
ArgumentStack, DEFAULT_OVERLAY_NAME, EngineState, ErrorHandlerStack, Redirection,
|
||||||
StackCallArgGuard, StackCollectValueGuard, StackIoGuard, StackOutDest,
|
StackCallArgGuard, StackCollectValueGuard, StackIoGuard, StackOutDest,
|
||||||
},
|
},
|
||||||
|
report_shell_warning,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use std::{
|
use std::{
|
||||||
@ -212,18 +213,17 @@ impl Stack {
|
|||||||
if let Some(value) = self.get_env_var(engine_state, "config") {
|
if let Some(value) = self.get_env_var(engine_state, "config") {
|
||||||
let old = self.get_config(engine_state);
|
let old = self.get_config(engine_state);
|
||||||
let mut config = (*old).clone();
|
let mut config = (*old).clone();
|
||||||
let error = config.update_from_value(&old, value);
|
let result = config.update_from_value(&old, value);
|
||||||
// The config value is modified by the update, so we should add it again
|
// The config value is modified by the update, so we should add it again
|
||||||
self.add_env_var("config".into(), config.clone().into_value(value.span()));
|
self.add_env_var("config".into(), config.clone().into_value(value.span()));
|
||||||
self.config = Some(config.into());
|
self.config = Some(config.into());
|
||||||
match error {
|
if let Some(warning) = result? {
|
||||||
None => Ok(()),
|
report_shell_warning(engine_state, &warning);
|
||||||
Some(err) => Err(err),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.config = None;
|
self.config = None;
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_var(&mut self, var_id: VarId, value: Value) {
|
pub fn add_var(&mut self, var_id: VarId, value: Value) {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::{ShellError, Span, Type};
|
use crate::{ShellError, Span, Type};
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -54,3 +56,29 @@ pub enum ConfigError {
|
|||||||
#[diagnostic(transparent)]
|
#[diagnostic(transparent)]
|
||||||
ShellError(#[from] ShellError),
|
ShellError(#[from] ShellError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Warnings which don't prevent config from being loaded, but we should inform the user about
|
||||||
|
#[derive(Clone, Debug, PartialEq, Error, Diagnostic)]
|
||||||
|
#[diagnostic(severity(Warning))]
|
||||||
|
pub enum ConfigWarning {
|
||||||
|
#[error("Incompatible options")]
|
||||||
|
#[diagnostic(code(nu::shell::incompatible_options), help("{help}"))]
|
||||||
|
IncompatibleOptions {
|
||||||
|
label: &'static str,
|
||||||
|
#[label = "{label}"]
|
||||||
|
span: Span,
|
||||||
|
help: &'static str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// To keep track of reported warnings
|
||||||
|
impl Hash for ConfigWarning {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
match self {
|
||||||
|
ConfigWarning::IncompatibleOptions { label, help, .. } => {
|
||||||
|
label.hash(state);
|
||||||
|
help.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
mod chained_error;
|
mod chained_error;
|
||||||
mod compile_error;
|
mod compile_error;
|
||||||
mod config_error;
|
mod config;
|
||||||
mod labeled_error;
|
mod labeled_error;
|
||||||
mod parse_error;
|
mod parse_error;
|
||||||
mod parse_warning;
|
mod parse_warning;
|
||||||
@ -9,7 +9,7 @@ pub mod shell_error;
|
|||||||
pub mod shell_warning;
|
pub mod shell_warning;
|
||||||
|
|
||||||
pub use compile_error::CompileError;
|
pub use compile_error::CompileError;
|
||||||
pub use config_error::ConfigError;
|
pub use config::{ConfigError, ConfigWarning};
|
||||||
pub use labeled_error::{ErrorLabel, LabeledError};
|
pub use labeled_error::{ErrorLabel, LabeledError};
|
||||||
pub use parse_error::{DidYouMean, ParseError};
|
pub use parse_error::{DidYouMean, ParseError};
|
||||||
pub use parse_warning::ParseWarning;
|
pub use parse_warning::ParseWarning;
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use crate::Span;
|
use crate::Span;
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{ReportMode, Reportable};
|
use crate::{ReportMode, Reportable};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Error, Diagnostic, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Error, Diagnostic)]
|
||||||
#[diagnostic(severity(Warning))]
|
#[diagnostic(severity(Warning))]
|
||||||
pub enum ParseWarning {
|
pub enum ParseWarning {
|
||||||
|
/// A parse-time deprectaion. Indicates that something will be removed in a future release.
|
||||||
|
///
|
||||||
|
/// Use [`ShellWarning::Deprecated`] if this is a deprecation which is only detectable at run-time.
|
||||||
#[error("{dep_type} deprecated.")]
|
#[error("{dep_type} deprecated.")]
|
||||||
#[diagnostic(code(nu::parser::deprecated))]
|
#[diagnostic(code(nu::parser::deprecated))]
|
||||||
Deprecated {
|
Deprecated {
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use crate::Span;
|
use crate::Span;
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{ReportMode, Reportable};
|
use crate::{ConfigWarning, ReportMode, Reportable};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Error, Diagnostic, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Error, Diagnostic)]
|
||||||
#[diagnostic(severity(Warning))]
|
#[diagnostic(severity(Warning))]
|
||||||
pub enum ShellWarning {
|
pub enum ShellWarning {
|
||||||
|
/// A parse-time deprectaion. Indicates that something will be removed in a future release.
|
||||||
|
///
|
||||||
|
/// Use [`ParseWarning::Deprecated`] if this is a deprecation which is detectable at parse-time.
|
||||||
#[error("{dep_type} deprecated.")]
|
#[error("{dep_type} deprecated.")]
|
||||||
#[diagnostic(code(nu::shell::deprecated))]
|
#[diagnostic(code(nu::shell::deprecated))]
|
||||||
Deprecated {
|
Deprecated {
|
||||||
@ -20,20 +22,20 @@ pub enum ShellWarning {
|
|||||||
help: Option<String>,
|
help: Option<String>,
|
||||||
report_mode: ReportMode,
|
report_mode: ReportMode,
|
||||||
},
|
},
|
||||||
}
|
/// Warnings reported while updating the config
|
||||||
|
#[error("Encountered {} warnings(s) when updating config", warnings.len())]
|
||||||
impl ShellWarning {
|
#[diagnostic(code(nu::shell::invalid_config))]
|
||||||
pub fn span(&self) -> Span {
|
InvalidConfig {
|
||||||
match self {
|
#[related]
|
||||||
ShellWarning::Deprecated { span, .. } => *span,
|
warnings: Vec<ConfigWarning>,
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Reportable for ShellWarning {
|
impl Reportable for ShellWarning {
|
||||||
fn report_mode(&self) -> ReportMode {
|
fn report_mode(&self) -> ReportMode {
|
||||||
match self {
|
match self {
|
||||||
ShellWarning::Deprecated { report_mode, .. } => *report_mode,
|
ShellWarning::Deprecated { report_mode, .. } => *report_mode,
|
||||||
|
ShellWarning::InvalidConfig { .. } => ReportMode::FirstUse,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +50,8 @@ impl Hash for ShellWarning {
|
|||||||
dep_type.hash(state);
|
dep_type.hash(state);
|
||||||
label.hash(state);
|
label.hash(state);
|
||||||
}
|
}
|
||||||
|
// We always report config warnings, so no hash necessary
|
||||||
|
ShellWarning::InvalidConfig { .. } => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,3 +168,11 @@ fn mutate_nu_config_plugin_gc_plugins() -> TestResult {
|
|||||||
"0sec",
|
"0sec",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mutate_nu_config_history_warning() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"$env.config.history.file_format = "plaintext"; $env.config.history.isolation = true"#,
|
||||||
|
"history isolation only compatible with SQLite format",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user