1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use crate::{Record, ShellError, Span, Value};
use std::{collections::HashMap, fmt::Display, str::FromStr};

pub(super) trait ReconstructVal {
    fn reconstruct_value(&self, span: Span) -> Value;
}

pub(super) fn process_string_enum<T, E>(
    config_point: &mut T,
    config_path: &[&str],
    value: &mut Value,
    errors: &mut Vec<ShellError>,
) where
    T: FromStr<Err = E> + ReconstructVal,
    E: Display,
{
    let span = value.span();
    if let Ok(v) = value.as_string() {
        match v.parse() {
            Ok(format) => {
                *config_point = format;
            }
            Err(err) => {
                errors.push(ShellError::GenericError {
                    error: "Error while applying config changes".into(),
                    msg: format!(
                        "unrecognized $env.config.{} option '{v}'",
                        config_path.join(".")
                    ),
                    span: Some(span),
                    help: Some(err.to_string()),
                    inner: vec![],
                });
                // Reconstruct
                *value = config_point.reconstruct_value(span);
            }
        }
    } else {
        errors.push(ShellError::GenericError {
            error: "Error while applying config changes".into(),
            msg: format!("$env.config.{} should be a string", config_path.join(".")),
            span: Some(span),
            help: Some("This value will be ignored.".into()),
            inner: vec![],
        });
        // Reconstruct
        *value = config_point.reconstruct_value(span);
    }
}

pub(super) fn process_bool_config(
    value: &mut Value,
    errors: &mut Vec<ShellError>,
    config_point: &mut bool,
) {
    if let Ok(b) = value.as_bool() {
        *config_point = b;
    } else {
        errors.push(ShellError::GenericError {
            error: "Error while applying config changes".into(),
            msg: "should be a bool".to_string(),
            span: Some(value.span()),
            help: Some("This value will be ignored.".into()),
            inner: vec![],
        });
        // Reconstruct
        *value = Value::bool(*config_point, value.span());
    }
}

pub(super) fn process_int_config(
    value: &mut Value,
    errors: &mut Vec<ShellError>,
    config_point: &mut i64,
) {
    if let Ok(b) = value.as_int() {
        *config_point = b;
    } else {
        errors.push(ShellError::GenericError {
            error: "Error while applying config changes".into(),
            msg: "should be an int".into(),
            span: Some(value.span()),
            help: Some("This value will be ignored.".into()),
            inner: vec![],
        });
        // Reconstruct
        *value = Value::int(*config_point, value.span());
    }
}

pub(super) fn report_invalid_key(keys: &[&str], span: Span, errors: &mut Vec<ShellError>) {
    // Because Value::Record discards all of the spans of its
    // column names (by storing them as Strings), the key name cannot be provided
    // as a value, even in key errors.
    errors.push(ShellError::GenericError {
        error: "Error while applying config changes".into(),
        msg: format!(
            "$env.config.{} is an unknown config setting",
            keys.join(".")
        ),
        span: Some(span),
        help: Some("This value will not appear in your $env.config record.".into()),
        inner: vec![],
    });
}

pub(super) fn report_invalid_value(msg: &str, span: Span, errors: &mut Vec<ShellError>) {
    errors.push(ShellError::GenericError {
        error: "Error while applying config changes".into(),
        msg: msg.into(),
        span: Some(span),
        help: Some("This value will be ignored.".into()),
        inner: vec![],
    });
}

pub(super) fn create_map(value: &Value) -> Result<HashMap<String, Value>, ShellError> {
    Ok(value
        .as_record()?
        .iter()
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect())
}

pub fn extract_value<'record>(
    name: &str,
    record: &'record Record,
    span: Span,
) -> Result<&'record Value, ShellError> {
    record
        .get(name)
        .ok_or_else(|| ShellError::MissingConfigValue {
            missing_value: name.to_string(),
            span,
        })
}