axconfig_gen/
value.rs

1use std::fmt;
2
3use toml_edit::Value;
4
5use crate::{ConfigErr, ConfigResult, ConfigType};
6
7/// A structure representing a config value.
8#[derive(Clone)]
9pub struct ConfigValue {
10    value: Value,
11    ty: Option<ConfigType>,
12}
13
14impl ConfigValue {
15    /// Parses a TOML-formatted string into a [`ConfigValue`].
16    pub fn new(s: &str) -> ConfigResult<Self> {
17        let value = s.parse::<Value>()?;
18        Self::from_raw_value(&value)
19    }
20
21    /// Parses a TOML-formatted string into a [`ConfigValue`] with a specified type.
22    pub fn new_with_type(s: &str, ty: &str) -> ConfigResult<Self> {
23        let value = s.parse::<Value>()?;
24        let ty = ConfigType::new(ty)?;
25        Self::from_raw_value_type(&value, ty)
26    }
27
28    pub(crate) fn from_raw_value(value: &Value) -> ConfigResult<Self> {
29        if !value_is_valid(value) {
30            return Err(ConfigErr::InvalidValue);
31        }
32        Ok(Self {
33            value: value.clone(),
34            ty: None,
35        })
36    }
37
38    pub(crate) fn from_raw_value_type(value: &Value, ty: ConfigType) -> ConfigResult<Self> {
39        if !value_is_valid(value) {
40            return Err(ConfigErr::InvalidValue);
41        }
42        if value_type_matches(value, &ty) {
43            Ok(Self {
44                value: value.clone(),
45                ty: Some(ty),
46            })
47        } else {
48            Err(ConfigErr::ValueTypeMismatch)
49        }
50    }
51
52    /// Returns the type of the config value if it is specified on construction.
53    pub fn ty(&self) -> Option<&ConfigType> {
54        self.ty.as_ref()
55    }
56
57    /// Updates the config value with a new value.
58    pub fn update(&mut self, new_value: Self) -> ConfigResult<()> {
59        match (&self.ty, &new_value.ty) {
60            (Some(ty), Some(new_ty)) => {
61                if ty != new_ty {
62                    return Err(ConfigErr::ValueTypeMismatch);
63                }
64            }
65            (Some(ty), None) => {
66                if !value_type_matches(&new_value.value, ty) {
67                    return Err(ConfigErr::ValueTypeMismatch);
68                }
69            }
70            (None, Some(new_ty)) => {
71                if !value_type_matches(&self.value, new_ty) {
72                    return Err(ConfigErr::ValueTypeMismatch);
73                }
74                self.ty = new_value.ty;
75            }
76            _ => {}
77        }
78        self.value = new_value.value;
79        Ok(())
80    }
81
82    /// Returns the inferred type of the config value.
83    pub fn inferred_type(&self) -> ConfigResult<ConfigType> {
84        inferred_type(&self.value)
85    }
86
87    /// Returns whether the type of the config value matches the specified type.
88    pub fn type_matches(&self, ty: &ConfigType) -> bool {
89        value_type_matches(&self.value, ty)
90    }
91
92    /// Returns the TOML-formatted string of the config value.
93    pub fn to_toml_value(&self) -> String {
94        to_toml(&self.value)
95    }
96
97    /// Returns the Rust code of the config value.
98    ///
99    /// The `indent` parameter specifies the number of spaces to indent the code.
100    pub fn to_rust_value(&self, ty: &ConfigType, indent: usize) -> ConfigResult<String> {
101        to_rust(&self.value, ty, indent)
102    }
103}
104
105impl fmt::Debug for ConfigValue {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
107        f.debug_struct("ConfigValue")
108            .field("value", &self.to_toml_value())
109            .field("type", &self.ty)
110            .finish()
111    }
112}
113
114fn is_num(s: &str) -> bool {
115    let s = s.to_lowercase().replace('_', "");
116    if let Some(s) = s.strip_prefix("0x") {
117        usize::from_str_radix(s, 16).is_ok()
118    } else if let Some(s) = s.strip_prefix("0b") {
119        usize::from_str_radix(s, 2).is_ok()
120    } else if let Some(s) = s.strip_prefix("0o") {
121        usize::from_str_radix(s, 8).is_ok()
122    } else {
123        s.parse::<usize>().is_ok()
124    }
125}
126
127fn value_is_valid(value: &Value) -> bool {
128    match value {
129        Value::Boolean(_) | Value::Integer(_) | Value::String(_) => true,
130        Value::Array(arr) => {
131            for e in arr {
132                if !value_is_valid(e) {
133                    return false;
134                }
135            }
136            true
137        }
138        _ => false,
139    }
140}
141
142fn value_type_matches(value: &Value, ty: &ConfigType) -> bool {
143    match (value, ty) {
144        (Value::Boolean(_), ConfigType::Bool) => true,
145        (Value::Integer(_), ConfigType::Int | ConfigType::Uint) => true,
146        (Value::String(s), _) => {
147            let s = s.value();
148            if is_num(s) {
149                matches!(ty, ConfigType::Int | ConfigType::Uint | ConfigType::String)
150            } else {
151                matches!(ty, ConfigType::String)
152            }
153        }
154        (Value::Array(arr), ConfigType::Tuple(ty)) => {
155            if arr.len() != ty.len() {
156                return false;
157            }
158            for (e, t) in arr.iter().zip(ty.iter()) {
159                if !value_type_matches(e, t) {
160                    return false;
161                }
162            }
163            true
164        }
165        (Value::Array(arr), ConfigType::Array(ty)) => {
166            for e in arr {
167                if !value_type_matches(e, ty) {
168                    return false;
169                }
170            }
171            true
172        }
173        _ => false,
174    }
175}
176
177fn inferred_type(value: &Value) -> ConfigResult<ConfigType> {
178    match value {
179        Value::Boolean(_) => Ok(ConfigType::Bool),
180        Value::Integer(i) => {
181            let val = *i.value();
182            if val < 0 {
183                Ok(ConfigType::Int)
184            } else {
185                Ok(ConfigType::Uint)
186            }
187        }
188        Value::String(s) => {
189            let s = s.value();
190            if is_num(s) {
191                Ok(ConfigType::Uint)
192            } else {
193                Ok(ConfigType::String)
194            }
195        }
196        Value::Array(arr) => {
197            let types = arr
198                .iter()
199                .map(inferred_type)
200                .collect::<ConfigResult<Vec<_>>>()?;
201            if types.is_empty() {
202                return Ok(ConfigType::Unknown);
203            }
204
205            let mut all_same = true;
206            for t in types.iter() {
207                if matches!(t, ConfigType::Unknown) {
208                    return Ok(ConfigType::Unknown);
209                }
210                if t != &types[0] {
211                    all_same = false;
212                    break;
213                }
214            }
215
216            if all_same {
217                Ok(ConfigType::Array(Box::new(types[0].clone())))
218            } else {
219                Ok(ConfigType::Tuple(types))
220            }
221        }
222        _ => Err(ConfigErr::InvalidValue),
223    }
224}
225
226pub fn to_toml(value: &Value) -> String {
227    match &value {
228        Value::Boolean(b) => b.display_repr().to_string(),
229        Value::Integer(i) => i.display_repr().to_string(),
230        Value::String(s) => s.display_repr().to_string(),
231        Value::Array(arr) => {
232            let elements = arr.iter().map(to_toml).collect::<Vec<_>>();
233            if arr.iter().any(|e| e.is_array()) {
234                format!("[\n    {}\n]", elements.join(",\n").replace("\n", "\n    "))
235            } else {
236                format!("[{}]", elements.join(", "))
237            }
238        }
239        _ => "".to_string(),
240    }
241}
242
243pub fn to_rust(value: &Value, ty: &ConfigType, indent: usize) -> ConfigResult<String> {
244    match (value, ty) {
245        (Value::Boolean(b), ConfigType::Bool) => Ok(b.display_repr().to_string()),
246        (Value::Integer(i), ConfigType::Int | ConfigType::Uint) => Ok(i.display_repr().to_string()),
247        (Value::String(s), _) => {
248            if matches!(ty, ConfigType::Int | ConfigType::Uint) {
249                Ok(s.value().to_string())
250            } else if matches!(ty, ConfigType::String) {
251                Ok(s.display_repr().to_string())
252            } else {
253                Err(ConfigErr::ValueTypeMismatch)
254            }
255        }
256        (Value::Array(arr), ConfigType::Tuple(ty)) => {
257            if arr.len() != ty.len() {
258                return Err(ConfigErr::ValueTypeMismatch);
259            }
260            let elements = arr
261                .iter()
262                .zip(ty)
263                .map(|(v, t)| to_rust(v, t, indent))
264                .collect::<ConfigResult<Vec<_>>>()?;
265            Ok(format!("({})", elements.join(", ")))
266        }
267        (Value::Array(arr), ConfigType::Array(ty)) => {
268            let elements = arr
269                .iter()
270                .map(|v| to_rust(v, ty, indent + 4))
271                .collect::<ConfigResult<Vec<_>>>()?;
272            let code = if arr.iter().any(|e| e.is_array()) {
273                let spaces = format!("\n{:indent$}", "", indent = indent + 4);
274                let spaces_end = format!(",\n{:indent$}", "", indent = indent);
275                format!(
276                    "&[{}{}{}]",
277                    spaces,
278                    elements.join(&format!(",{}", spaces)),
279                    spaces_end
280                )
281            } else {
282                format!("&[{}]", elements.join(", "))
283            };
284            Ok(code)
285        }
286        _ => Err(ConfigErr::ValueTypeMismatch),
287    }
288}