arma_rs/value/
from_arma.rs

1use crate::Value;
2
3fn split_array(s: &str) -> Vec<String> {
4    let mut nest = 0;
5    let mut parts = Vec::new();
6    let mut part = String::new();
7    for c in s.chars() {
8        if c == '[' {
9            part.push(c);
10            nest += 1;
11        } else if c == ']' {
12            nest -= 1;
13            part.push(c);
14        } else if c == ',' && nest == 0 {
15            parts.push(part.trim().to_string());
16            part = String::new();
17        } else {
18            part.push(c);
19        }
20    }
21    let part = part.trim().to_string();
22    if !part.is_empty() {
23        parts.push(part);
24    }
25    parts
26}
27
28/// Error type for [`FromArma`]
29#[derive(Debug, PartialEq, Eq)]
30pub enum FromArmaError {
31    /// Invalid [`crate::Value`]
32    InvalidValue(String),
33    /// Invalid primitive value
34    InvalidPrimitive(String),
35    /// Collection size mismatch
36    InvalidLength {
37        /// Expected size
38        expected: usize,
39        /// Actual size
40        actual: usize,
41    },
42
43    /// Missing opening(true) or closing(false) bracket
44    MissingBracket(bool),
45    /// Missing field
46    MissingField(String),
47    /// Unknown field
48    UnknownField(String),
49    /// Duplicate field
50    DuplicateField(String),
51
52    /// Custom error message
53    Custom(String),
54}
55
56impl std::fmt::Display for FromArmaError {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            Self::InvalidValue(s) => write!(f, "invalid value: {s}"),
60            Self::InvalidPrimitive(s) => write!(f, "error parsing primitive: {s}"),
61            Self::InvalidLength { expected, actual } => {
62                write!(f, "expected {expected} elements, got {actual}")
63            }
64            Self::MissingBracket(start) => {
65                if *start {
66                    write!(f, "missing '[' at start of array")
67                } else {
68                    write!(f, "missing ']' at end of array")
69                }
70            }
71            Self::MissingField(s) => write!(f, "missing field: {s}"),
72            Self::UnknownField(s) => write!(f, "unknown field: {s}"),
73            Self::DuplicateField(s) => write!(f, "duplicate field: {s}"),
74            Self::Custom(s) => f.write_str(s),
75        }
76    }
77}
78
79impl FromArmaError {
80    /// Creates a new [`FromArmaError::Custom`]
81    pub fn custom(msg: impl std::fmt::Display) -> Self {
82        Self::Custom(msg.to_string())
83    }
84}
85
86/// A trait for converting a value from Arma to a Rust value.
87pub trait FromArma: Sized {
88    /// Converts a value from Arma to a Rust value.
89    /// # Errors
90    /// Will return an error if the value cannot be converted.
91    fn from_arma(s: String) -> Result<Self, FromArmaError>;
92}
93
94#[cfg(not(any(test, doc, debug_assertions)))]
95impl FromArma for String {
96    fn from_arma(s: String) -> Result<Self, FromArmaError> {
97        let Some(s) = s.strip_prefix('"').and_then(|s| s.strip_suffix('"')) else {
98            return Err(FromArmaError::InvalidPrimitive(String::from(
99                "missing '\"' at start or end of string",
100            )));
101        };
102        Ok(s.replace("\"\"", "\""))
103    }
104}
105
106#[cfg(any(test, doc, debug_assertions))]
107impl FromArma for String {
108    fn from_arma(s: String) -> Result<Self, FromArmaError> {
109        let s = s
110            .strip_prefix('"')
111            .and_then(|s| s.strip_suffix('"'))
112            .unwrap_or(&s);
113        Ok(s.replace("\"\"", "\""))
114    }
115}
116
117macro_rules! impl_from_arma {
118    ($($t:ty),*) => {
119        $(
120            impl FromArma for $t {
121                fn from_arma(s: String) -> Result<Self, FromArmaError> {
122                    let s = s.strip_suffix('"').and_then(|s| s.strip_prefix('"')).unwrap_or(&s);
123                    s.parse::<Self>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))
124                }
125            }
126        )*
127    };
128}
129impl_from_arma!(f32, f64, bool, char);
130
131macro_rules! impl_from_arma_number {
132    ($($t:ty),*) => {
133        $(
134            impl FromArma for $t {
135                fn from_arma(s: String) -> Result<Self, FromArmaError> {
136                    let s = s.strip_suffix('"').and_then(|s| s.strip_prefix('"')).unwrap_or(&s);
137                    if s.contains("e") {
138                        // parse exponential notation
139                        let mut parts = s.split('e');
140                        let base = match parts.next().unwrap() {
141                            s if !s.is_empty() => s.parse::<f64>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))?,
142                            _ => return Err(FromArmaError::InvalidPrimitive("invalid number literal".to_string())),
143                        };
144                        let exp = match parts.next().unwrap() {
145                            s if !s.is_empty() => s.parse::<i32>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))?,
146                            _ => return Err(FromArmaError::InvalidPrimitive("invalid number literal".to_string())),
147                        };
148                        return Ok((base * 10.0_f64.powi(exp)) as $t);
149                    }
150                    s.parse::<Self>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))
151                }
152            }
153        )*
154    };
155}
156impl_from_arma_number!(
157    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
158);
159
160macro_rules! impl_from_arma_tuple {
161    { $c: expr, $($t:ident)* } => {
162        impl<$($t),*> FromArma for ($($t),*)
163        where
164            $($t: FromArma),*
165        {
166            fn from_arma(s: String) -> Result<Self, FromArmaError> {
167                let v: [Value; $c] = FromArma::from_arma(s)?;
168                let mut iter = v.iter();
169                Ok((
170                    $($t::from_arma(iter.next().unwrap().to_string())?),*
171                ))
172            }
173        }
174    };
175}
176
177impl_from_arma_tuple! { 2, A B }
178impl_from_arma_tuple! { 3, A B C }
179impl_from_arma_tuple! { 4, A B C D }
180impl_from_arma_tuple! { 5, A B C D E }
181impl_from_arma_tuple! { 6, A B C D E F }
182impl_from_arma_tuple! { 7, A B C D E F G }
183impl_from_arma_tuple! { 8, A B C D E F G H }
184impl_from_arma_tuple! { 9, A B C D E F G H I }
185impl_from_arma_tuple! { 10, A B C D E F G H I J }
186impl_from_arma_tuple! { 11, A B C D E F G H I J K }
187impl_from_arma_tuple! { 12, A B C D E F G H I J K L }
188impl_from_arma_tuple! { 13, A B C D E F G H I J K L M }
189impl_from_arma_tuple! { 14, A B C D E F G H I J K L M N }
190impl_from_arma_tuple! { 15, A B C D E F G H I J K L M N O }
191impl_from_arma_tuple! { 16, A B C D E F G H I J K L M N O P }
192impl_from_arma_tuple! { 17, A B C D E F G H I J K L M N O P Q }
193impl_from_arma_tuple! { 18, A B C D E F G H I J K L M N O P Q R }
194impl_from_arma_tuple! { 19, A B C D E F G H I J K L M N O P Q R S }
195impl_from_arma_tuple! { 20, A B C D E F G H I J K L M N O P Q R S T }
196impl_from_arma_tuple! { 21, A B C D E F G H I J K L M N O P Q R S T U }
197impl_from_arma_tuple! { 22, A B C D E F G H I J K L M N O P Q R S T U V }
198impl_from_arma_tuple! { 23, A B C D E F G H I J K L M N O P Q R S T U V W }
199impl_from_arma_tuple! { 24, A B C D E F G H I J K L M N O P Q R S T U V W X }
200impl_from_arma_tuple! { 25, A B C D E F G H I J K L M N O P Q R S T U V W X Y }
201impl_from_arma_tuple! { 26, A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
202
203impl<T> FromArma for Vec<T>
204where
205    T: FromArma,
206{
207    fn from_arma(s: String) -> Result<Self, FromArmaError> {
208        let source = s
209            .strip_prefix('[')
210            .ok_or(FromArmaError::MissingBracket(true))?
211            .strip_suffix(']')
212            .ok_or(FromArmaError::MissingBracket(false))?;
213        let parts = split_array(source);
214        parts.iter().try_fold(Self::new(), |mut acc, p| {
215            acc.push(T::from_arma(p.clone())?);
216            Ok(acc)
217        })
218    }
219}
220
221impl<T, const N: usize> FromArma for [T; N]
222where
223    T: FromArma,
224{
225    fn from_arma(s: String) -> Result<Self, FromArmaError> {
226        let v: Vec<T> = FromArma::from_arma(s)?;
227        let len = v.len();
228        v.try_into().map_err(|_| FromArmaError::InvalidLength {
229            expected: N,
230            actual: len,
231        })
232    }
233}
234
235impl<K, V, S> FromArma for std::collections::HashMap<K, V, S>
236where
237    K: FromArma + Eq + std::hash::Hash,
238    V: FromArma,
239    S: std::hash::BuildHasher + Default,
240{
241    fn from_arma(s: String) -> Result<Self, FromArmaError> {
242        let data: Vec<(K, V)> = FromArma::from_arma(s)?;
243        let mut ret = Self::default();
244        for (k, v) in data {
245            ret.insert(k, v);
246        }
247        Ok(ret)
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254    use crate::Value;
255
256    #[test]
257    fn parse_tuple_varying_types() {
258        assert_eq!(
259            (String::from("hello"), 123),
260            <(String, i32)>::from_arma(r#"["hello", 123]"#.to_string()).unwrap()
261        );
262        assert!(<(String, String)>::from_arma(r#"["hello", 123, "world"]"#.to_string()).is_err());
263    }
264
265    #[test]
266    fn parse_tuple_size_errors() {
267        assert_eq!(
268            <(String, i32)>::from_arma(r"[]".to_string()),
269            Err(FromArmaError::InvalidLength {
270                expected: 2,
271                actual: 0
272            })
273        );
274        assert_eq!(
275            <(String, i32)>::from_arma(r#"["hello"]"#.to_string()),
276            Err(FromArmaError::InvalidLength {
277                expected: 2,
278                actual: 1
279            })
280        );
281        assert_eq!(
282            <(String, i32)>::from_arma(r#"["hello", 123, 456]"#.to_string()),
283            Err(FromArmaError::InvalidLength {
284                expected: 2,
285                actual: 3
286            })
287        );
288    }
289
290    #[test]
291    fn parse_tuple_bracket_errors() {
292        assert_eq!(
293            <(String, i32)>::from_arma(r#"["hello", 123"#.to_string()),
294            Err(FromArmaError::MissingBracket(false))
295        );
296        assert_eq!(
297            <(String, i32)>::from_arma(r#""hello", 123"#.to_string()),
298            Err(FromArmaError::MissingBracket(true))
299        );
300    }
301
302    #[test]
303    fn test_tuple_2() {
304        assert_eq!(
305            (0, 1),
306            <(u8, u8)>::from_arma(r"[0, 1]".to_string()).unwrap()
307        );
308    }
309
310    #[test]
311    fn test_tuple_3() {
312        assert_eq!(
313            (0, 1, 2),
314            <(u8, u8, u8)>::from_arma(r"[0, 1, 2]".to_string()).unwrap()
315        );
316    }
317
318    #[test]
319    fn test_tuple_4() {
320        assert_eq!(
321            (0, 1, 2, 3),
322            <(u8, u8, u8, u8)>::from_arma(r"[0, 1, 2, 3]".to_string()).unwrap()
323        );
324    }
325
326    #[test]
327    fn test_tuple_5() {
328        assert_eq!(
329            (0, 1, 2, 3, 4),
330            <(u8, u8, u8, u8, u8)>::from_arma(r"[0, 1, 2, 3, 4]".to_string()).unwrap()
331        );
332    }
333
334    #[test]
335    fn test_tuple_6() {
336        assert_eq!(
337            (0, 1, 2, 3, 4, 5),
338            <(u8, u8, u8, u8, u8, u8)>::from_arma(r"[0, 1, 2, 3, 4, 5]".to_string()).unwrap()
339        );
340    }
341
342    #[test]
343    fn test_tuple_7() {
344        assert_eq!(
345            (0, 1, 2, 3, 4, 5, 6),
346            <(u8, u8, u8, u8, u8, u8, u8)>::from_arma(r"[0, 1, 2, 3, 4, 5, 6]".to_string())
347                .unwrap()
348        );
349    }
350
351    #[test]
352    fn test_tuple_8() {
353        assert_eq!(
354            (0, 1, 2, 3, 4, 5, 6, 7),
355            <(u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(r"[0, 1, 2, 3, 4, 5, 6, 7]".to_string())
356                .unwrap()
357        );
358    }
359
360    #[test]
361    fn test_tuple_9() {
362        assert_eq!(
363            (0, 1, 2, 3, 4, 5, 6, 7, 8),
364            <(u8, u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(
365                r"[0, 1, 2, 3, 4, 5, 6, 7, 8]".to_string()
366            )
367            .unwrap()
368        );
369    }
370
371    #[test]
372    fn test_tuple_10() {
373        assert_eq!(
374            (0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
375            <(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(
376                r"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]".to_string()
377            )
378            .unwrap()
379        );
380    }
381
382    #[test]
383    fn parse_string() {
384        assert_eq!(
385            String::from("hello"),
386            <String>::from_arma(r#""hello""#.to_string()).unwrap()
387        );
388        assert_eq!(
389            String::from("\"hello\""),
390            <String>::from_arma(r#"""hello"""#.to_string()).unwrap()
391        );
392        assert_eq!(
393            String::from(r#"hello "john"."#),
394            <String>::from_arma(r#""hello ""john"".""#.to_string()).unwrap()
395        );
396    }
397
398    #[test]
399    fn parse_vec() {
400        assert_eq!(
401            vec![String::from("hello"), String::from("bye"),],
402            <Vec<String>>::from_arma(r#"["hello","bye"]"#.to_string()).unwrap()
403        );
404        assert_eq!(
405            vec![String::from("hello"), String::from("world")],
406            <Vec<String>>::from_arma(r#"[hello, "world"]"#.to_string()).unwrap()
407        );
408    }
409
410    #[test]
411    fn parse_vec_bracket_errors() {
412        assert_eq!(
413            <Vec<String>>::from_arma(r#""hello","bye"]"#.to_string()),
414            Err(FromArmaError::MissingBracket(true))
415        );
416        assert_eq!(
417            <Vec<String>>::from_arma(r#"["hello","bye""#.to_string()),
418            Err(FromArmaError::MissingBracket(false))
419        );
420    }
421
422    #[test]
423    fn parse_vec_tuple() {
424        assert_eq!(
425            (vec![(String::from("hello"), 123), (String::from("bye"), 321),]),
426            <Vec<(String, i32)>>::from_arma(r#"[["hello", 123],["bye", 321]]"#.to_string())
427                .unwrap()
428        );
429    }
430
431    #[test]
432    fn parse_slice() {
433        assert_eq!(
434            vec![String::from("hello"), String::from("bye"),],
435            <[String; 2]>::from_arma(r#"["hello","bye"]"#.to_string()).unwrap()
436        );
437    }
438
439    #[test]
440    fn parse_slice_size_errors() {
441        assert_eq!(
442            <[String; 2]>::from_arma(r"[]".to_string()),
443            Err(FromArmaError::InvalidLength {
444                expected: 2,
445                actual: 0
446            })
447        );
448        assert_eq!(
449            <[String; 2]>::from_arma(r#"["hello"]"#.to_string()),
450            Err(FromArmaError::InvalidLength {
451                expected: 2,
452                actual: 1
453            })
454        );
455        assert_eq!(
456            <[String; 2]>::from_arma(r#"["hello","bye","world"]"#.to_string()),
457            Err(FromArmaError::InvalidLength {
458                expected: 2,
459                actual: 3
460            })
461        );
462    }
463
464    #[test]
465    fn parse_hashmap() {
466        assert_eq!(
467            std::collections::HashMap::from([
468                (String::from("hello"), 123),
469                (String::from("bye"), 321),
470            ]),
471            <std::collections::HashMap<String, i32>>::from_arma(
472                r#"[["hello", 123],["bye",321]]"#.to_string()
473            )
474            .unwrap()
475        );
476
477        assert_eq!(
478            std::collections::HashMap::from([
479                (String::from("hello"), 123),
480                (String::from("bye"), 321),
481                (String::from("hello"), 321),
482            ]),
483            <std::collections::HashMap<String, i32>>::from_arma(
484                r#"[["hello", 123],["bye",321],["hello", 321]]"#.to_string()
485            )
486            .unwrap()
487        );
488    }
489
490    #[test]
491    fn parse_exponential() {
492        assert_eq!(1.0e-10, <f64>::from_arma(r"1.0e-10".to_string()).unwrap());
493        assert_eq!(
494            1_227_700,
495            <u32>::from_arma(r"1.2277e+006".to_string()).unwrap()
496        );
497    }
498
499    #[test]
500    fn parse_exponential_errors() {
501        assert_eq!(
502            <f64>::from_arma(r"e-10".to_string()),
503            Err(FromArmaError::InvalidPrimitive(
504                "invalid float literal".to_string()
505            ))
506        );
507        assert_eq!(
508            <f64>::from_arma(r"1.0e".to_string()),
509            Err(FromArmaError::InvalidPrimitive(
510                "invalid float literal".to_string()
511            ))
512        );
513
514        assert_eq!(
515            <u32>::from_arma(r"e-10".to_string()),
516            Err(FromArmaError::InvalidPrimitive(
517                "invalid number literal".to_string()
518            ))
519        );
520        assert_eq!(
521            <u32>::from_arma(r"1.0e".to_string()),
522            Err(FromArmaError::InvalidPrimitive(
523                "invalid number literal".to_string()
524            ))
525        );
526    }
527
528    #[test]
529    fn parse_value_tuple() {
530        assert_eq!(
531            (
532                Value::String(String::from("hello")),
533                Value::String(String::from("world"))
534            ),
535            <(Value, Value)>::from_arma(r#"["hello", "world"]"#.to_string()).unwrap()
536        );
537    }
538
539    #[test]
540    fn parse_value_vec() {
541        assert_eq!(
542            vec![
543                Value::String(String::from("hello")),
544                Value::String(String::from("world"))
545            ],
546            <Vec<Value>>::from_arma(r#"["hello", "world"]"#.to_string()).unwrap()
547        );
548    }
549
550    #[test]
551    fn parse_float() {
552        assert_eq!(1.0, <f64>::from_arma(r#"1.0"#.to_string()).unwrap());
553        assert_eq!(1.0, <f64>::from_arma(r#"1"#.to_string()).unwrap());
554        assert_eq!(1.0, <f64>::from_arma(r#"1.0e+0"#.to_string()).unwrap());
555        assert_eq!(-1.0, <f64>::from_arma(r#"-1.0"#.to_string()).unwrap());
556        assert_eq!(1.0, <f64>::from_arma(r#""1.0""#.to_string()).unwrap());
557        assert_eq!(1.0, <f64>::from_arma(r#""1""#.to_string()).unwrap());
558        assert_eq!(1.0, <f64>::from_arma(r#""1.0e+0""#.to_string()).unwrap());
559        assert_eq!(-1.0, <f64>::from_arma(r#""-1.0""#.to_string()).unwrap());
560    }
561}