nu-protocol 0.115.0

Nushell's internal protocols, including its abstract syntax tree
Documentation
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();
        // Common config.nu mistake: `{ show_banner false }` is a closure (missing `:`),
        // not a record — point users at field syntax instead of only "got closure".
        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,
        });
    }

    // We'll probably need this again in the future so allow dead code for now
    #[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),
        }
    }
}