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