1use std::collections::HashMap;
5
6use crate::{Value, RuneError};
7use crate::ast::ObjectItem;
8
9impl TryFrom<Value> for String {
10 type Error = RuneError;
11
12 fn try_from(value: Value) -> Result<Self, Self::Error> {
13 match value {
14 Value::String(s) => Ok(s),
15 _ => Err(RuneError::TypeError {
16 message: format!("Expected string, got {:?}", value),
17 line: 0,
18 column: 0,
19 hint: Some("Use a string value in your config".into()),
20 code: Some(401),
21 }),
22 }
23 }
24}
25
26impl TryFrom<Value> for f64 {
27 type Error = RuneError;
28
29 fn try_from(value: Value) -> Result<Self, Self::Error> {
30 match value {
31 Value::Number(n) => Ok(n),
32 _ => Err(RuneError::TypeError {
33 message: format!("Expected number, got {:?}", value),
34 line: 0,
35 column: 0,
36 hint: Some("Use a number value in your config".into()),
37 code: Some(402),
38 }),
39 }
40 }
41}
42
43impl TryFrom<Value> for f32 {
44 type Error = RuneError;
45
46 fn try_from(value: Value) -> Result<Self, Self::Error> {
47 match value {
48 Value::Number(n) => Ok(n as f32),
49 _ => Err(RuneError::TypeError {
50 message: format!("Expected number, got {:?}", value),
51 line: 0,
52 column: 0,
53 hint: Some("Use a number value in your config".into()),
54 code: Some(402),
55 }),
56 }
57 }
58}
59
60impl TryFrom<Value> for i32 {
61 type Error = RuneError;
62
63 fn try_from(value: Value) -> Result<Self, Self::Error> {
64 match value {
65 Value::Number(n) => Ok(n as i32),
66 _ => Err(RuneError::TypeError {
67 message: format!("Expected number, got {:?}", value),
68 line: 0,
69 column: 0,
70 hint: Some("Use a number value in your config".into()),
71 code: Some(402),
72 }),
73 }
74 }
75}
76
77impl TryFrom<Value> for i64 {
78 type Error = RuneError;
79
80 fn try_from(value: Value) -> Result<Self, Self::Error> {
81 match value {
82 Value::Number(n) => Ok(n as i64),
83 _ => Err(RuneError::TypeError {
84 message: format!("Expected number, got {:?}", value),
85 line: 0,
86 column: 0,
87 hint: Some("Use a number value in your config".into()),
88 code: Some(402),
89 }),
90 }
91 }
92}
93
94impl TryFrom<Value> for u8 {
95 type Error = RuneError;
96
97 fn try_from(value: Value) -> Result<Self, Self::Error> {
98 match value {
99 Value::Number(n) => {
100 if n >= 0.0 && n <= u8::MAX as f64 {
101 Ok(n as u8)
102 } else {
103 Err(RuneError::TypeError {
104 message: format!("Number {} out of range for u8", n),
105 line: 0,
106 column: 0,
107 hint: Some("Use a number between 0 and 255".into()),
108 code: Some(407),
109 })
110 }
111 }
112 _ => Err(RuneError::TypeError {
113 message: format!("Expected number, got {:?}", value),
114 line: 0,
115 column: 0,
116 hint: Some("Use a number value in your config".into()),
117 code: Some(402),
118 }),
119 }
120 }
121}
122
123impl TryFrom<Value> for u16 {
124 type Error = RuneError;
125
126 fn try_from(value: Value) -> Result<Self, Self::Error> {
127 match value {
128 Value::Number(n) => {
129 if n >= 0.0 && n <= u16::MAX as f64 {
130 Ok(n as u16)
131 } else {
132 Err(RuneError::TypeError {
133 message: format!("Number {} out of range for u16", n),
134 line: 0,
135 column: 0,
136 hint: Some("Use a number between 0 and 65535".into()),
137 code: Some(403),
138 })
139 }
140 }
141 _ => Err(RuneError::TypeError {
142 message: format!("Expected number, got {:?}", value),
143 line: 0,
144 column: 0,
145 hint: Some("Use a number value in your config".into()),
146 code: Some(402),
147 }),
148 }
149 }
150}
151
152impl TryFrom<Value> for u32 {
153 type Error = RuneError;
154
155 fn try_from(value: Value) -> Result<Self, Self::Error> {
156 match value {
157 Value::Number(n) => {
158 if n >= 0.0 && n <= u32::MAX as f64 {
159 Ok(n as u32)
160 } else {
161 Err(RuneError::TypeError {
162 message: format!("Number {} out of range for u32", n),
163 line: 0,
164 column: 0,
165 hint: Some("Use a number between 0 and 4294967295".into()),
166 code: Some(408),
167 })
168 }
169 }
170 _ => Err(RuneError::TypeError {
171 message: format!("Expected number, got {:?}", value),
172 line: 0,
173 column: 0,
174 hint: Some("Use a number value in your config".into()),
175 code: Some(402),
176 }),
177 }
178 }
179}
180
181impl TryFrom<Value> for u64 {
182 type Error = RuneError;
183
184 fn try_from(value: Value) -> Result<Self, Self::Error> {
185 match value {
186 Value::Number(n) => {
187 if n >= 0.0 && n <= u64::MAX as f64 {
188 Ok(n as u64)
189 } else {
190 Err(RuneError::TypeError {
191 message: format!("Number {} out of range for u64", n),
192 line: 0,
193 column: 0,
194 hint: Some("Use a positive number within u64 range".into()),
195 code: Some(406),
196 })
197 }
198 }
199 _ => Err(RuneError::TypeError {
200 message: format!("Expected number, got {:?}", value),
201 line: 0,
202 column: 0,
203 hint: Some("Use a number value in your config".into()),
204 code: Some(402),
205 }),
206 }
207 }
208}
209
210impl TryFrom<Value> for usize {
211 type Error = RuneError;
212
213 fn try_from(value: Value) -> Result<Self, Self::Error> {
214 match value {
215 Value::Number(n) => {
216 if n >= 0.0 && n.is_finite() {
217 Ok(n as usize)
218 } else {
219 Err(RuneError::TypeError {
220 message: format!("Number {} out of range for usize", n),
221 line: 0,
222 column: 0,
223 hint: Some("Use a positive integer".into()),
224 code: Some(409),
225 })
226 }
227 }
228 _ => Err(RuneError::TypeError {
229 message: format!("Expected number, got {:?}", value),
230 line: 0,
231 column: 0,
232 hint: Some("Use a number value in your config".into()),
233 code: Some(402),
234 }),
235 }
236 }
237}
238
239impl TryFrom<Value> for bool {
240 type Error = RuneError;
241
242 fn try_from(value: Value) -> Result<Self, Self::Error> {
243 match value {
244 Value::Bool(b) => Ok(b),
245 Value::Reference(ref path) if path.len() == 1 => {
246 let ref_name = &path[0];
247 if ref_name.to_lowercase().starts_with("tru") || ref_name.to_lowercase().starts_with("fal") {
248 Err(RuneError::TypeError {
249 message: format!("Invalid boolean value '{}'. Did you mean 'true' or 'false'?", ref_name),
250 line: 0,
251 column: 0,
252 hint: None,
253 code: Some(404),
254 })
255 } else {
256 Err(RuneError::TypeError {
257 message: format!("Expected boolean (true/false), got reference to '{}'", ref_name),
258 line: 0,
259 column: 0,
260 hint: None,
261 code: Some(404),
262 })
263 }
264 }
265 _ => Err(RuneError::TypeError {
266 message: format!("Expected boolean, got {:?}", value),
267 line: 0,
268 column: 0,
269 hint: None,
270 code: Some(404),
271 }),
272 }
273 }
274}
275
276impl<T> TryFrom<Value> for Vec<T>
277where
278 T: TryFrom<Value, Error = RuneError>,
279{
280 type Error = RuneError;
281
282 fn try_from(value: Value) -> Result<Self, Self::Error> {
283 match value {
284 Value::Array(arr) => {
285 let mut result = Vec::new();
286 for item in arr {
287 result.push(T::try_from(item)?);
288 }
289 Ok(result)
290 }
291 _ => Err(RuneError::TypeError {
292 message: format!("Expected array, got {:?}", value),
293 line: 0,
294 column: 0,
295 hint: Some("Use an array [...] in your config".into()),
296 code: Some(405),
297 }),
298 }
299 }
300}
301
302impl<T> TryFrom<Value> for Option<T>
303where
304 T: TryFrom<Value, Error = RuneError>,
305{
306 type Error = RuneError;
307
308 fn try_from(value: Value) -> Result<Self, Self::Error> {
309 match value {
310 Value::Null => Ok(None),
311 v => Ok(Some(T::try_from(v)?)),
312 }
313 }
314}
315
316fn object_items_to_map(items: Vec<ObjectItem>) -> Result<HashMap<String, Value>, RuneError> {
324 let mut map = HashMap::new();
325
326 for item in items {
327 match item {
328 ObjectItem::Assign(key, val) => {
329 map.insert(key, val);
330 }
331 ObjectItem::IfBlock(_) => {
332 return Err(RuneError::TypeError {
333 message: "Expected object with only key/value pairs, but found an if-block".into(),
334 line: 0,
335 column: 0,
336 hint: Some("This usually means if-blocks were not resolved. Ensure you resolve/evaluate the config before converting it to a HashMap.".into()),
337 code: Some(410),
338 });
339 }
340 }
341 }
342
343 Ok(map)
344}
345
346impl TryFrom<Value> for HashMap<String, Value> {
347 type Error = RuneError;
348
349 fn try_from(value: Value) -> Result<Self, Self::Error> {
350 match value {
351 Value::Object(items) => object_items_to_map(items),
352 _ => Err(RuneError::TypeError {
353 message: format!("Expected object, got {:?}", value),
354 line: 0,
355 column: 0,
356 hint: Some("Use an object block in your config".into()),
357 code: Some(410),
358 }),
359 }
360 }
361}
362
363impl TryFrom<Value> for HashMap<String, String> {
364 type Error = RuneError;
365
366 fn try_from(value: Value) -> Result<Self, Self::Error> {
367 match value {
368 Value::Object(items) => {
369 let base = object_items_to_map(items)?;
370 let mut map = HashMap::new();
371 for (key, val) in base {
372 let string_val = String::try_from(val)?;
373 map.insert(key, string_val);
374 }
375 Ok(map)
376 }
377 _ => Err(RuneError::TypeError {
378 message: format!("Expected object, got {:?}", value),
379 line: 0,
380 column: 0,
381 hint: Some("Use an object block with string values".into()),
382 code: Some(410),
383 }),
384 }
385 }
386}
387
388impl TryFrom<Value> for (String, String) {
389 type Error = RuneError;
390
391 fn try_from(value: Value) -> Result<Self, Self::Error> {
392 match value {
393 Value::Array(arr) if arr.len() == 2 => {
394 let first = String::try_from(arr[0].clone())?;
395 let second = String::try_from(arr[1].clone())?;
396 Ok((first, second))
397 }
398 _ => Err(RuneError::TypeError {
399 message: "Expected array with exactly 2 string elements".into(),
400 line: 0,
401 column: 0,
402 hint: Some("Use [\"key\", \"value\"] format".into()),
403 code: Some(411),
404 }),
405 }
406 }
407}
408
409impl TryFrom<Value> for (String, Value) {
410 type Error = RuneError;
411
412 fn try_from(value: Value) -> Result<Self, Self::Error> {
413 match value {
414 Value::Array(arr) if arr.len() == 2 => {
415 let key = String::try_from(arr[0].clone())?;
416 let val = arr[1].clone();
417 Ok((key, val))
418 }
419 _ => Err(RuneError::TypeError {
420 message: "Expected array with exactly 2 elements (key and value)".into(),
421 line: 0,
422 column: 0,
423 hint: Some("Use [\"key\", value] format".into()),
424 code: Some(411),
425 }),
426 }
427 }
428}
429
430impl RuneError {
431 pub fn file_error(message: String, path: String) -> Self {
435 RuneError::FileError {
436 message,
437 path,
438 hint: Some("Check file path and permissions".into()),
439 code: Some(300),
440 }
441 }
442}