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)) => {
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 pub fn inferred_type(&self) -> ConfigResult<ConfigType> {
84 inferred_type(&self.value)
85 }
86
87 pub fn type_matches(&self, ty: &ConfigType) -> bool {
89 value_type_matches(&self.value, ty)
90 }
91
92 pub fn to_toml_value(&self) -> String {
94 to_toml(&self.value)
95 }
96
97 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}