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