servo_config/
pref_util.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
9pub enum PrefValue {
10    Float(f64),
11    Int(i64),
12    UInt(u64),
13    Str(String),
14    Bool(bool),
15    Array(Vec<PrefValue>),
16}
17
18impl PrefValue {
19    pub fn from_booleanish_str(input: &str) -> Self {
20        match input {
21            "false" => PrefValue::Bool(false),
22            "true" => PrefValue::Bool(true),
23            _ => input
24                .parse::<i64>()
25                .map(PrefValue::Int)
26                .or_else(|_| input.parse::<f64>().map(PrefValue::Float))
27                .unwrap_or_else(|_| PrefValue::from(input)),
28        }
29    }
30}
31
32impl TryFrom<&Value> for PrefValue {
33    type Error = String;
34
35    fn try_from(value: &Value) -> Result<Self, Self::Error> {
36        match value {
37            Value::Null => Err("Cannot turn null into preference".into()),
38            Value::Bool(value) => Ok((*value).into()),
39            Value::Number(number) => number
40                .as_i64()
41                .map(Into::into)
42                .or_else(|| number.as_f64().map(Into::into))
43                .map(Ok)
44                .unwrap_or(Err("Could not parse number from JSON".into())),
45            Value::String(value) => Ok(value.clone().into()),
46            Value::Array(array) => {
47                let array = array
48                    .iter()
49                    .map(TryInto::<PrefValue>::try_into)
50                    .collect::<Result<Vec<_>, _>>()?;
51                Ok(PrefValue::Array(array))
52            },
53            Value::Object(_) => Err("Cannot turn object into preference".into()),
54        }
55    }
56}
57
58macro_rules! impl_pref_from {
59    ($($t: ty => $variant: path,)*) => {
60        $(
61            impl From<$t> for PrefValue {
62                fn from(other: $t) -> Self {
63                    $variant(other.into())
64                }
65            }
66        )+
67    }
68}
69
70macro_rules! impl_from_pref {
71    ($($variant: path => $t: ty,)*) => {
72        $(
73            impl TryFrom<PrefValue> for $t {
74                type Error = String;
75                fn try_from(other: PrefValue) -> Result<Self, Self::Error> {
76                    match other {
77                        $variant(value) => Ok(value.into()),
78                        _ => Err(format!("Cannot convert {other:?} to {}", std::any::type_name::<$t>())),
79                    }
80                }
81            }
82        )+
83    }
84}
85
86impl_pref_from! {
87    f64 => PrefValue::Float,
88    i64 => PrefValue::Int,
89    u64 => PrefValue::UInt,
90    String => PrefValue::Str,
91    &str => PrefValue::Str,
92    bool => PrefValue::Bool,
93}
94
95impl_from_pref! {
96    PrefValue::Float => f64,
97    PrefValue::Int => i64,
98    PrefValue::UInt => u64,
99    PrefValue::Str => String,
100    PrefValue::Bool => bool,
101}
102
103impl From<[f64; 4]> for PrefValue {
104    fn from(other: [f64; 4]) -> PrefValue {
105        PrefValue::Array(IntoIterator::into_iter(other).map(|v| v.into()).collect())
106    }
107}
108
109impl From<PrefValue> for [f64; 4] {
110    fn from(other: PrefValue) -> [f64; 4] {
111        match other {
112            PrefValue::Array(values) if values.len() == 4 => {
113                let values: Vec<f64> = values
114                    .into_iter()
115                    .map(TryFrom::try_from)
116                    .filter_map(Result::ok)
117                    .collect();
118                if values.len() == 4 {
119                    [values[0], values[1], values[2], values[3]]
120                } else {
121                    panic!(
122                        "Cannot convert PrefValue to {:?}",
123                        std::any::type_name::<[f64; 4]>()
124                    )
125                }
126            },
127            _ => panic!(
128                "Cannot convert {:?} to {:?}",
129                other,
130                std::any::type_name::<[f64; 4]>()
131            ),
132        }
133    }
134}
135
136#[cfg(test)]
137mod test {
138    use super::*;
139
140    #[test]
141    fn test_pref_value_from_str() {
142        let value = PrefValue::from_booleanish_str("21");
143        assert_eq!(value, PrefValue::Int(21));
144
145        let value = PrefValue::from_booleanish_str("12.5");
146        assert_eq!(value, PrefValue::Float(12.5));
147
148        let value = PrefValue::from_booleanish_str("a string");
149        assert_eq!(value, PrefValue::Str("a string".into()));
150
151        let value = PrefValue::from_booleanish_str("false");
152        assert_eq!(value, PrefValue::Bool(false));
153
154        let value = PrefValue::from_booleanish_str("true");
155        assert_eq!(value, PrefValue::Bool(true));
156    }
157}