nu_protocol/config/
helper.rs

1use super::error::ConfigErrors;
2use crate::{Record, ShellError, Span, Type, Value};
3use std::{
4    borrow::Borrow,
5    collections::HashMap,
6    fmt::{self, Display},
7    hash::Hash,
8    ops::{Deref, DerefMut},
9    str::FromStr,
10};
11
12pub(super) struct ConfigPath<'a> {
13    components: Vec<&'a str>,
14}
15
16impl<'a> ConfigPath<'a> {
17    pub fn new() -> Self {
18        Self {
19            components: vec!["$env.config"],
20        }
21    }
22
23    pub fn push(&mut self, key: &'a str) -> ConfigPathScope<'_, 'a> {
24        self.components.push(key);
25        ConfigPathScope { inner: self }
26    }
27}
28
29impl Display for ConfigPath<'_> {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "{}", self.components.join("."))
32    }
33}
34
35pub(super) struct ConfigPathScope<'whole, 'part> {
36    inner: &'whole mut ConfigPath<'part>,
37}
38
39impl Drop for ConfigPathScope<'_, '_> {
40    fn drop(&mut self) {
41        self.inner.components.pop();
42    }
43}
44
45impl<'a> Deref for ConfigPathScope<'_, 'a> {
46    type Target = ConfigPath<'a>;
47
48    fn deref(&self) -> &Self::Target {
49        self.inner
50    }
51}
52
53impl DerefMut for ConfigPathScope<'_, '_> {
54    fn deref_mut(&mut self) -> &mut Self::Target {
55        self.inner
56    }
57}
58
59pub(super) trait UpdateFromValue: Sized {
60    fn update<'a>(
61        &mut self,
62        value: &'a Value,
63        path: &mut ConfigPath<'a>,
64        errors: &mut ConfigErrors,
65    );
66}
67
68impl UpdateFromValue for Value {
69    fn update(&mut self, value: &Value, _path: &mut ConfigPath, _errors: &mut ConfigErrors) {
70        *self = value.clone();
71    }
72}
73
74impl UpdateFromValue for bool {
75    fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
76        if let Ok(val) = value.as_bool() {
77            *self = val;
78        } else {
79            errors.type_mismatch(path, Type::Bool, value);
80        }
81    }
82}
83
84impl UpdateFromValue for i64 {
85    fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
86        if let Ok(val) = value.as_int() {
87            *self = val;
88        } else {
89            errors.type_mismatch(path, Type::Int, value);
90        }
91    }
92}
93
94impl UpdateFromValue for usize {
95    fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
96        if let Ok(val) = value.as_int() {
97            if let Ok(val) = val.try_into() {
98                *self = val;
99            } else {
100                errors.invalid_value(path, "a non-negative integer", value);
101            }
102        } else {
103            errors.type_mismatch(path, Type::Int, value);
104        }
105    }
106}
107
108impl UpdateFromValue for String {
109    fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
110        if let Ok(val) = value.as_str() {
111            *self = val.into();
112        } else {
113            errors.type_mismatch(path, Type::String, value);
114        }
115    }
116}
117
118impl<K, V> UpdateFromValue for HashMap<K, V>
119where
120    K: Borrow<str> + for<'a> From<&'a str> + Eq + Hash,
121    V: Default + UpdateFromValue,
122{
123    fn update<'a>(
124        &mut self,
125        value: &'a Value,
126        path: &mut ConfigPath<'a>,
127        errors: &mut ConfigErrors,
128    ) {
129        if let Ok(record) = value.as_record() {
130            *self = record
131                .iter()
132                .map(|(key, val)| {
133                    let mut old = self.remove(key).unwrap_or_default();
134                    old.update(val, &mut path.push(key), errors);
135                    (key.as_str().into(), old)
136                })
137                .collect();
138        } else {
139            errors.type_mismatch(path, Type::record(), value);
140        }
141    }
142}
143
144pub(super) fn config_update_string_enum<T>(
145    choice: &mut T,
146    value: &Value,
147    path: &mut ConfigPath,
148    errors: &mut ConfigErrors,
149) where
150    T: FromStr,
151    T::Err: Display,
152{
153    if let Ok(str) = value.as_str() {
154        match str.parse() {
155            Ok(val) => *choice = val,
156            Err(err) => errors.invalid_value(path, err.to_string(), value),
157        }
158    } else {
159        errors.type_mismatch(path, Type::String, value);
160    }
161}
162
163pub fn extract_value<'record>(
164    column: &'static str,
165    record: &'record Record,
166    span: Span,
167) -> Result<&'record Value, ShellError> {
168    record
169        .get(column)
170        .ok_or_else(|| ShellError::MissingRequiredColumn { column, span })
171}