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